Compare commits

..
129 Commits
Author SHA1 Message Date
Jason Mitchell 4910cfd6d3 Fix some CMEs and black screens seen during boot/splash. Should be minimal overhead after splash. (#1959) 2026-07-23 19:33:01 -07:00
Jason Mitchell d1c6677caf Fix plascrete/cleanroom trophy rendering (#1957) 2026-07-23 13:42:46 -07:00
Jason Mitchell 8cf0296284 Properly gate end flash (#1954) 2026-07-23 09:34:41 -07:00
Jason Mitchell 4fd98e5026 Add some missing buffer redirects (#1953) 2026-07-23 08:34:11 -07:00
SpacEagle17 8e293f13fb Fix at_midBlock at high coordinates (#1949) 2026-07-22 18:02:55 -07:00
Jason Mitchell 2db20ad866 Handle some Intel driver shenanigans (#1951) 2026-07-21 08:05:46 -07:00
Eclipse-Sol 10a5b550f2 Add Uniforms for End Flashes (#1892) 2026-07-18 06:42:57 -07:00
Eldrinn-Elantey c1403dcf13 Reload unicode glyph widths on resource pack change (#1944) 2026-07-18 05:58:45 -07:00
tiffit 5d1fabfca9 Add API for allowing other mods to have celeritas support (#1936) 2026-07-16 09:54:55 -05:00
tiffit 555b2b7dd2 Fix typo in PR template (#1937) 2026-07-14 00:10:41 -05:00
SuperSouprandJason Mitchell 7017786a7c CTM integration improvements (#1931)
Co-authored-by: Jason Mitchell <[email protected]>
2026-07-12 10:45:16 -05:00
Eclipse-Sol 1c49649685 Fix Streams not rendering correctly (#1932) 2026-07-12 10:41:36 -05:00
Caedis 2bae79c4ee Up default zoom speed to 20 (#1910) 2026-07-09 21:34:03 -04:00
Jason Mitchell c22935660a Reenable compute shaders (#1918) 2026-07-05 20:38:13 -07:00
tiffit 79c488102b CTM: Fix "flipped" value not set to false in setFace() (#1913) 2026-07-05 18:24:28 -07:00
Jason Mitchell cc1353b73a Fix infinite farplane causing incorrect culling in pdim with no clouds (#1917) 2026-07-05 18:18:17 -07:00
Eclipse-Sol d1e6dc8c94 Fix/bug fixes (#1905) 2026-07-04 13:06:36 -07:00
GribanovIvan ecbf8fea2f font: load bundled .ttf/.otf fonts from fontfiles/ and config/angelia/fonts/ (#1904) 2026-07-04 12:16:48 -07:00
tiffit 610209605f Compact CTM: Decouple block face and rendered face (#1907) 2026-07-04 03:22:04 +03:00
mist475andJason Mitchell 112aae3735 Fix lotr biome colours again (essentially copy of https://github.com/GTNewHorizons/Angelica/pull/25) (#1902)
Co-authored-by: Jason Mitchell <[email protected]>
2026-07-03 14:21:05 -07:00
RecursivePineapple 5844b1b5c1 Cubic Chunks Compatibility (#1808) 2026-07-03 12:41:04 -07:00
Jason Mitchell 05f01c17e6 No point continuously building clouds if we're not rendering them... (#1900) 2026-06-29 20:17:06 -07:00
SpacEagle17 f07d96316a Add shader setting search functionality and center selection (#1897) 2026-06-29 19:36:55 -07:00
Jason Mitchell 5b704d1bc2 Fix space elevator cable (#1896) 2026-06-28 16:33:58 -07:00
Jason Mitchell 26135124f4 Support more glDrawElement overloads in display lists (#1895) 2026-06-28 15:07:24 -07:00
Jason Mitchell 3abc89b7b2 Betweenlands fix (#1894) 2026-06-28 13:46:10 -07:00
Jason Mitchell 03d908d658 Update readme and version detectors (#1893) 2026-06-27 15:47:32 -07:00
Eclipse-Sol 76bf19c8f6 Majority shader fixes (#1868) 2026-06-27 14:48:51 -07:00
Eclipse-Sol b27834cbd9 Narrow and make damage overlay a config option (#1886) 2026-06-27 10:12:39 -07:00
sisyphussyandJason Mitchell eb0ecb0483 Fix deps (#1888)
Co-authored-by: Jason Mitchell <[email protected]>
2026-06-27 10:11:30 -07:00
Caedis 9acb0e67a7 feat: add smooth zoom option (#1890) 2026-06-27 09:57:21 -07:00
Jason Mitchell f4f9b56b17 End portal fix (#1891) 2026-06-27 09:56:21 -07:00
Midnight145 2196439e6c ClientEvent framework (primarily written for LiteloaderLoader) (#1843) 2026-06-25 20:13:18 -07:00
RedNicStone dd68c64239 Fix map rendering issue in xaero's map mods (#1880) 2026-06-21 14:13:11 -07:00
Jason Mitchell 0decc515e4 Fix universium shader (#1877) 2026-06-18 23:42:21 -07:00
tiffit b3e1e89c88 Add new CTM mode "compact_expanded" to generate textures on runtime (#1840)
* Rewrite compact CTM to generate the textures ahead of time

* fix animated textures not working

* only interpolate if notfine is enabled

* some bugfixes

* bring back old compact ctm and make a new compact_expanded

* bug fixes
2026-06-18 12:04:45 +03:00
Jason Mitchell 32bff85526 Handle glDrawArraysInstanced and variants, as well as dynamic TE bounds. (#1874) 2026-06-17 20:18:07 -07:00
Auynonymous 02fc95f239 Make Glass CTM Compact, Add Stained Glass Compact CTM (#1873) 2026-06-17 16:00:58 -07:00
MalTeeez 07fd6d78a0 Separate out entity overlay render mixins (#1872) 2026-06-17 19:55:12 +00:00
Jason Mitchell 87aa853f81 Attempt to fix deferred particle batching (#1871) 2026-06-16 22:16:26 -07:00
Jason Mitchell ab69eea654 Announce releases (#1867) 2026-06-15 14:50:47 -07:00
Jason Mitchell edccf63b66 Bump glsl-transformation-lib and buildscript (#1865) 2026-06-14 18:09:05 -07:00
MalTeeez 80413fb3de Add policy for AI contributions & a PR template (#1864) 2026-06-15 01:40:03 +02:00
Jason Mitchell f3ed9aa82b Force invalidate dh depth tex when reconnecting DH Textures (#1862) 2026-06-14 14:59:01 -07:00
Jason Mitchell 307c563b6d Rework DH matrix handling (#1861) 2026-06-14 14:20:19 -07:00
Eclipse-Sol f92ce1d24e Fix enchant glint fix so that it looks proper (#1856) 2026-06-13 17:28:12 -07:00
sisyphussy a7fac16d97 VAO stuff (#1858) 2026-06-13 17:26:09 -07:00
Jason Mitchell 20c3caaed4 A few redirects for calls that DH was using that we weren't redirecting (#1859) 2026-06-13 16:50:44 -07:00
Jason Mitchell bf08cd50d9 Support glPixelStore in display lists (#1854) 2026-06-11 20:29:14 -07:00
Eclipse-Sol 805a266059 Support extended metas for NEID and EID (#1853) 2026-06-11 17:58:22 -07:00
Eclipse-Sol d3c9963280 Fix ctm crash due to null (#1852) 2026-06-11 18:02:08 -04:00
Eclipse-Sol 4630bc4fd8 Fix shaders not loading the correct dim (#1844) 2026-06-09 23:12:13 -07:00
Jason Mitchell ce32a3d3df Emulate glTexParameter(pname=GL_GENERATE_MIPMAP) on core (#1845) 2026-06-09 23:03:42 -07:00
Eclipse-Sol 1991dc58ec Fix Steadfast not loading right (#1838) 2026-06-08 20:06:29 -07:00
Eclipse-Sol ae19236e1a Remove force hand animation (#1839) 2026-06-08 20:05:49 -07:00
Kotmatross 7ad368c937 Be less lazy (#1834) 2026-06-08 17:03:36 -07:00
Eclipse-Sol 5c86c87bcf Allow devs to target the dragon death rays as its own entity id (#1836) 2026-06-08 17:03:05 -07:00
tiffit 99ba7fa775 Remove CTM adding .png to tile paths (#1832) 2026-06-07 16:05:10 -07:00
Eclipse-Sol 5ae4ab8c5c More specific shader fixes pertaining to EP (#1831) 2026-06-07 11:19:30 -07:00
madoha ddd68e023f Add Japanese Localizations (#1830) 2026-06-07 11:14:20 -07:00
Eclipse-Sol f78cfdd5fb Update and fix Iris' shader menus (#1828) 2026-06-06 17:12:38 -07:00
Jason Mitchell 183bcfbf41 Narrow more transformer exclusions (#1827) 2026-06-06 15:38:05 -07:00
tiffit 502f30c0f9 Fix bottom-face being flipped for compact CTM (#1820) 2026-06-05 16:14:14 -07:00
Jason Mitchell ceff04adba Fixes a regression with TFC. (#1822) 2026-06-05 16:13:30 -07:00
Eclipse-Sol b4dfdb8cc5 Fix beacon beams (#1821) 2026-06-05 16:03:46 -07:00
Eclipse-Sol 3064818477 Link up block outline shader to iris shaders (#1814) 2026-06-05 11:25:31 -07:00
Eclipse-Sol 27524e6a89 Add double_plant handling to match modern (#1815) 2026-06-05 08:36:14 -07:00
Eclipse-Sol 5d5e910fc0 Match modern hand swap animations (#1816) 2026-06-05 08:35:07 -07:00
Jason Mitchellandsisyphussy f325fd7fc3 Be more lazy (#1794)
* Be more lazy

---------

Co-authored-by: sisyphussy <[email protected]>
2026-06-05 08:29:27 -07:00
Jason Mitchell ec278d3d7a Upload fewer things (#1811) 2026-06-04 13:11:59 -07:00
Caedis eef3d2f50a feat(hand): add cancellable RenderHandEvent for canRender (#1806) 2026-06-01 20:38:27 +00:00
tiffit 3da597ab18 Improve perf of EmissiveTextureAutoloader.resourceExists (#1805) 2026-06-01 13:34:23 -07:00
Jason Mitchell 0e79380618 More class loading order fun with java 8... (#1804) 2026-05-31 20:56:00 -07:00
tiffit b639d5c646 Add compat for hodgepodge's async icon loading (#1799) 2026-05-31 15:28:27 -07:00
Jason Mitchell 0d209d426d Document why I did this so I remember next time someone asks about this cursed function (#1802) 2026-05-31 15:12:45 -07:00
danyadev bf24084033 switch to new RFB transform signature (#1421) 2026-05-29 09:35:31 -07:00
tiffit 9267ecc735 Remove registerIcons call in MCPF MixinTextureMap (#1795) 2026-05-28 13:12:10 -07:00
dcc8d8deed Make shader based clouds work with iris shaderpacks, and port optimizations from #1685 (#1793)
* Make shader based clouds work with iris shaderpacks, and port optimizations from #1685

Co-Authored-By: Eclipse-Sol <[email protected]>

* Yeet GL_QUADS

* Optimize a little more

---------

Co-authored-by: Eclipse-Sol <[email protected]>
Co-authored-by: Logan Phillips <[email protected]>
2026-05-28 10:31:17 -04:00
Jason Mitchell 5dc4f1fab8 Fix tests (#1791) 2026-05-27 18:41:41 -07:00
CaedisandJason Mitchell d3cea1bfe4 fix: refresh ClonedChunkSection biome data on cache hit (#1769)
Co-authored-by: Jason Mitchell <[email protected]>
2026-05-27 16:45:29 -07:00
DanandRanzu 5db2b248d7 add per-segment drop shadow (&u) (#1754)
Co-authored-by: Ranzu <[email protected]>
2026-05-27 16:44:54 -07:00
Eclipse-Sol 21ab526284 Fix shadow map from being clobbered on Windows (#1789) 2026-05-27 16:43:51 -07:00
MassAnarchy 4c8ea679ef Fix GL_FEEDBACK readback using correct attribute 0 VBO (#1783) 2026-05-27 16:43:03 -07:00
Eclipse-Sol 2317379df2 Add gbuffer_lightning (#1787) 2026-05-27 16:41:51 -07:00
Death Fuel 24dae49c7f Mark ClientFixer as incompatible (#1786) 2026-05-26 16:03:22 -07:00
Eclipse-Sol 865de3cbab Fix issues that directly affects Euphoria Patches + Complementary (#1782) 2026-05-26 14:11:33 -07:00
Jason Mitchell 9c45549e35 Resolve generic compressed image formats (#1781) 2026-05-25 11:50:18 -07:00
Jason Mitchell 2a20198292 Fixes crash with FFP shader linking (#1780) 2026-05-24 17:31:46 -07:00
Eclipse-SolandJason Mitchell f0b68f93af Port modern's damage overlay (#1777)
Co-authored-by: Jason Mitchell <[email protected]>
2026-05-24 10:48:42 -07:00
Eclipse-SolandJason Mitchell dd1d3aa518 Add NBT matching to list of valid block/item entries (#1771)
Co-authored-by: Jason Mitchell <[email protected]>
2026-05-23 23:08:48 -07:00
Jason Mitchell c9633fd745 Fix osx crash (#1778) 2026-05-23 22:03:59 -07:00
DarkShadow44 e2e94b2958 Fix DH framebuffer UNSUPPORTED after resize (#1741) 2026-05-23 21:19:45 -07:00
tiffit 3046b043a0 clear block and item renderlist on resource reload (#1773) 2026-05-23 17:45:31 -07:00
Apis035andCaedis 8a6c8035db Add view bobbing mode (#1761)
* Add view bobbing mode

* Fix missing import

---------

Co-authored-by: Caedis <[email protected]>
2026-05-21 23:11:35 +02:00
Alexdoru 2b15370c00 Properly do the fml/rfb registration for IsbrhTessellatorAbuseTransformer (#1770) 2026-05-20 09:56:16 -07:00
Jason Mitchell 3ca9e32191 Stop burying the error due to things catching RuntimeExceptions.... (#1767) 2026-05-19 08:53:05 -07:00
sisyphussy efbca29381 Add cached block item renderer (#1730) 2026-05-19 08:03:51 -07:00
Jason Mitchell 9c1b6c75ff Fix crash on windows (#1766) 2026-05-19 08:02:37 -07:00
sisyphussy fa91b70528 Display list stuff (#1750) 2026-05-18 12:46:48 -07:00
Jason Mitchell 61de8385ca Minecraft is a dirty dirty liar. (#1764) 2026-05-18 12:39:30 -07:00
Jason Mitchell 962e7527a4 Register later (#1763) 2026-05-18 12:39:20 -07:00
Jason Mitchell 2ab2713fe9 Stops draw/startDrawing in ISBRH (#1759) 2026-05-17 21:08:18 -07:00
Jason Mitchell 7cf652cd4e Misc fixes - Journeymap and Xaeros (#1756) 2026-05-17 10:23:10 -07:00
Jason Mitchell 48eba2fd96 Properly end the dynamic lighting profiler section (#1751) 2026-05-16 12:20:09 -07:00
Apis035 356ba376fe Adjust options screen width (#1748) 2026-05-15 08:47:28 -07:00
Eclipse-Sol ef6b0742a9 Fix skeleton legs from being jammed into their pelvis (#1747) 2026-05-14 15:26:25 -07:00
Kaiz 787929d0bd Handle recoverable block render failures in celeritas meshing (#1705) 2026-05-10 10:00:43 -07:00
0e1fd9d0e9 add auto emissive overlay for any block (#1710)
Co-authored-by: VortexSo4 <[email protected]>
Co-authored-by: ah-OOG-ah <[email protected]>
2026-05-10 02:12:08 -04:00
Ruling-0 58938e82a7 Compact CTM Fixes (#1726) 2026-05-09 11:42:42 -07:00
Branan Riley be6c8c85b5 Detect vanilla AO at a higher level (#1733) 2026-05-09 11:41:59 -07:00
ABKQPOandCaedis 39bca37d0d Adjust IDynamicLightProducer to allow passing an ItemStack parameter (#1734)
Co-authored-by: Caedis <[email protected]>
2026-05-08 20:31:22 -07:00
czqwq 1c61be04a7 Fix Already tesselating logs (#1729) 2026-05-08 20:30:08 -07:00
7a97cd9386 Add RGB color system (#1669)
Co-authored-by: Death Fuel <[email protected]>
Co-authored-by: Jason Mitchell <[email protected]>
2026-05-08 18:36:51 -07:00
Kotmatross 5b09bde7a0 [NTM:Space] Fix mixins in obfuscated environment (#1738) 2026-05-08 18:24:29 +02:00
Ruling-0 edc0081b7c Fix CTM inventory texture bleed (#1721) 2026-05-04 12:25:51 -07:00
Alexdoru 913256b032 fix world client leak in CTMUtils (#1724) 2026-05-04 12:24:25 -07:00
sisyphussy e1f5083d3e Fix intel detection (#1722) 2026-05-04 12:23:50 -07:00
sisyphussy b55e693ad2 Remove DSA from GLSM & other stuff (#1723) 2026-05-04 12:23:38 -07:00
sisyphussy 21c01a1aa4 Fix terrible performance on Intel (#1718) 2026-05-03 14:32:58 -07:00
Caedis 83a26b2291 Add Cosmetic Armor support for Dynamic Lights (#1708) 2026-05-03 14:31:49 -07:00
Kotmatross 7e44c1efcb Fix stale uniforms when switching shader program (#1715) 2026-05-03 14:30:46 -07:00
VortexSo4 155f209db5 remove ctm guide as it is worse than wiki (#1706) 2026-04-30 11:03:18 -07:00
SpacEagle17 c4a6c165a0 Increase MAX_COLOR_BUFFERS limit to 32 (#1704) 2026-04-28 12:33:51 -07:00
Eclipse-Sol 624fe6b453 Add targeting of snow-covered blocks. (#1696) 2026-04-26 15:26:10 -07:00
YoshyandJason Mitchell fd45fda0ab Fix AO Lighting for compact CTM (#1697)
Co-authored-by: Jason Mitchell <[email protected]>
2026-04-26 12:55:24 -07:00
Alexdoru 00fc97a611 Fix dynamic lights world client leaks (#1700) 2026-04-26 10:01:46 -07:00
Alexdoru 445dc487b9 fix world client leak caused by lambda in IrisExclusiveUniforms$WorldInfoUniforms (#1701) 2026-04-26 09:56:08 -07:00
Yoshy f43fbd4d9f Fix CTM to not spread to nearby TEs (#1695) 2026-04-25 13:09:38 -07:00
Jason Mitchell dbabc4b3dc Maybe fix DragonAPI on OSX - take two (#1689)
* Maybe fix DragonAPI on OSX - take two
* Bind a default VAO on the SharedDrawable
* Mixin that tries to swap out glGetProgramInfoLog when appropriate

* no direct gl calls
2026-04-21 15:12:02 -07:00
520 changed files with 15854 additions and 5568 deletions

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
# Description of your *PR* and other info
<!-- Please include a summary of the change -->
<!-- Any details that you think are important to review this PR? -->
<!-- Are there any Issues / PRs related to this one? -->
<!-- Add a screenshot or a video demonstration for new features -->
<details open><summary>Template</summary> <!-- OPEN is intentional -->
### Before submitting a *pull request* make sure you have:
- [ ] Read the [AI Contribution Policy](https://github.com/GTNewHorizons/Angelica/blob/master/AI_POLICY.md)
- [ ] At least [skimmed](https://github.com/GTNewHorizons/Angelica/search?q=is%3Apr&type=Issues) the tracker for similar pull requests
</details>
+40
View File
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
: "${REPO:?REPO required}"
: "${TAG:?TAG required}"
if [ -z "${DISCORD_WEBHOOK_URL:-}" ]; then
echo "DISCORD_WEBHOOK_URL not set, skipping"
exit 0
fi
if ! release_json=$(gh release view "$TAG" --repo "$REPO" --json body,url,publishedAt 2>&1); then
echo "Could not read release $TAG in $REPO; skipping:"
printf '%s\n' "$release_json"
exit 0
fi
body=$(jq -r '.body // ""' <<<"$release_json")
url=$(jq -r '.url' <<<"$release_json")
ts=$(jq -r '.publishedAt // ""' <<<"$release_json")
[ -n "$ts" ] || ts=$(date -u +%Y-%m-%dT%H:%M:%SZ)
payload=$(jq -n \
--arg title "$TAG" \
--arg url "$url" \
--arg desc "$body" \
--arg ts "$ts" \
'{
embeds: [{
title: ("Angelica " + $title + " released"),
url: $url,
description: (if ($desc | length) > 3900
then $desc[0:3900] + "\n\n...[full changelog](" + $url + ")"
else $desc end),
color: 3066993,
footer: { text: "GitHub Releases" },
timestamp: $ts
}]
}')
curl -fsS -X POST -H "Content-Type: application/json" -d "$payload" "$DISCORD_WEBHOOK_URL"
+19
View File
@@ -12,3 +12,22 @@ jobs:
release-tags:
uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/release-tags.yml@master
secrets: inherit
notify-discord:
needs: [release-tags]
runs-on: ubuntu-24.04
if: >-
${{ !endsWith(github.ref_name, '-snapshot')
&& !contains(github.event.head_commit.message, '[snapshot]')
&& !endsWith(github.ref_name, '-pre') }}
steps:
- name: Checkout
uses: actions/checkout@v5
- name: Announce release to Discord
shell: bash
env:
DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_RELEASE_WEBHOOK_URL }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REPO: ${{ github.repository }}
TAG: ${{ github.ref_name }}
run: bash .github/scripts/announce-release.sh
+1
View File
@@ -12,6 +12,7 @@ build/
/config/
/crash-reports/
/logs/
tmp/
options.txt
/saves/
usernamecache.json
+55
View File
@@ -0,0 +1,55 @@
# AI Usage Policy
This project has strict rules for AI usage:
* **The human-in-the-loop must fully understand all code.** If you
can't explain what your changes do and how they interact with the
greater system without the aid of AI tools, do not contribute
those changes.
* **Issues and discussions can use AI assistance but must have a full
human-in-the-loop.** This means that any content generated with AI
must have been reviewed *and edited* by a human before submission.
The author must do their research and trim this down.
* **No AI-generated media is allowed (textures, videos, audio, etc.).**
Text and code are the only acceptable AI-generated content, per the
other rules in this policy.
* **Commit messages, PR descriptions must read in your own voice.**
No AI-authored or AI co-authored commits, including Co-authored-by AI tags.
* **Only contribute fixes for bugs you have personally hit in-game.**
If you haven’t experienced or reproduced a bug firsthand, and don’t
already know the relevant code well, don’t throw AI at it and forward it to us.
Patches written for problems the author never actually experienced themselves
tend to fix the wrong thing and/or produce another.
* **The voice and intent of written content must be yours.**
For documentation and especially quests, the tone and the point you're trying to make
has to come from you, not an AI. Use AI to help with phrasing or cleanup if you like,
but if the substance and voice aren't yours, don't contribute it.
* **Bad AI use may be blocked.** People who produce bad contributions
that are clearly AI (slop) may be blocked from making future contributions.
We love to help junior developers learn and grow, but if you're interested
in that, don't use AI and we'll help you. We're sorry that bad AI
users have ruined this for you.
## There are Humans Here
Please remember that this project is maintained by humans.
Every discussion, issue, and pull request is read and reviewed by
humans (and sometimes machines, too). It is a boundary point at which
people interact with each other and work is done. It is rude and
disrespectful to approach this boundary with low-effort, unqualified
work, since it puts the burden of validation on the maintainer.
In a perfect world, AI would produce high-quality, accurate work
every time. But today, that reality depends on the user of the AI.
And today, many users of AI do not use it well. So, until that
changes, we need to have strict rules to protect maintainers.
Based on https://github.com/ghostty-org/ghostty/blob/main/AI_POLICY.md
-302
View File
@@ -1,302 +0,0 @@
# CTM Guide<img src="img/logo-readme.png" align="right">
###### by Vortex [@vortex_so in discord, any suggestions are accepted]<br>
This file describes how to use CTM, structure of `.properties` file and some useful (or not) techniques
### Note:
- All textures for CTM textures are in `[modname]/mcpatcher/ctm` folder<br>
the overrides apply globally so it doesn't matter in which directory you put them<br>
- in the `ctm` folder you must have a folder with your block name (or any name actually)<br>
in that folder you will have all texture files (animations allowed via mcmeta file for every texture) and `.properties` file<br>
multiple `.properties` files are allowed for a single folder and set of textures for more complex behaviour (see examples)
## Quick Links
- **[Method](#method)**
- **[MatchBlocks](#matchblocks)**
- **[MatchTiles](#matchtiles)**
- **[Tiles](#tiles)**
- **[Connect](#connect)**
- **[Faces](#faces)**
- **[Biomes](#biomes)**
- **[Heights](#heights)**
- **[RenderPass](#renderpass)**
- **[InnerSeams](#innerseams)**
- **[Weights](#weights)**<br>
- **[Examples](#examples)**
## Structure of `.properties` files
---
## `matchBlocks`
Override for a specific block, you can get the value from the tooltip if you have this feature enabled.<br>
Can accept several values which allows to apply one CTM for different blocks.<br>
if no value for this property was provided, Angelica will fallback to name of the `.property` file
examples:<br>
`matchBlocks=gregtech:gt.blockcasings4`<br>
`matchBlocks=minecraft:glass minecraft:glass_pane`
---
## `matchTiles`
Override for a specific texture, main difference from matchBlocks is that this will only match a certain texture, not the block itself<br>
example:<br>
`matchTiles:minecraft:blocks/stone`
---
## `tiles`
The list of texture names for CTM. Supports ranges and explicit names.<br>
You probably would want to use texture names like `0.png`, `1.png` as it is easier, however this allows for any names
examples:<br>
`tiles=0-46` - classic for full CTM, expects files from `0.png` to `46.png`<br>
`tiles=0-3,5, 8` - expects files `0.png`, `1.png`, `2.png`, `3.png`, `5.png`, `8.png`<br>
`tiles=sometext,somemoretext` - expects files `sometext.png`, `somemoretext.png`
---
## `connect`
Sets how the blocks are connected, can accept values: `block`, `tile`, `material`<br>
`block` connects based on block (who would guess)<br>
`tile` connects based on texture<br>
`material` connects based on material (didn't really understand this part, ig it is for stone, iron, wood etc)<br>
Fallbacks to `block` if no value for `matchTiles` is set, otherwise fallbacks to `tile`<br>
Most of the time you don't need to set this value because of that fallback rule
example:<br>
`connect=block`
---
## `faces`
Limit to specific faces of a block, can accept several values<br>
Fallbacks to `all` if value not present in the `.properties` file
examples:<br>
`faces=top`<br>
`faces=all`<br>
`faces=bottom`<br>
`faces=sides`<br>
`faces=north south east west`<br>
`faces=top bottom sides`
---
## `biomes`
Limit to specific biomes, can accept several values<br>
Fallbacks to all if value not present in the `.properties` file
examples:<br>
`biomes=plains`<br>
`biomes=desert forest`<br>
`biomes=taiga jungle savanna`
---
## `heights`
Limit to certain Y levels, can only accept several values<br>
can be set through either `minHeight` and `maxHeight` or `heights`
examples:<br>
`heights=0-64`<br>
`heights=64-255`<br>
example of maxHeight and minHeight:<br>
`minHeight=64`<br>
`maxHeight=128`
---
## `renderPass`
Controls which rendering layer a CTM rule is applied in<br>
In most cases you only need it to avoid rendering issues with transparency
`renderPass=solid` used for fully opaque blocks<br>
`renderPass=cutout` used for fences, glass CTM, textured blocks with hard transparency<br>
`renderPass=translucent` used for transparent blocks like glass or water<br>
`renderPass=cutout_mipped` no idea for what<br>
`renderPass=overlay` used for blocks with an overlay, for example GT hatches or damaged anvils<br>
`renderPass=backface` used for blocks with a backface, however it is very rare to use this one
---
## `innerSeams`
Adds internal seams, fallback to `innerSeams=false` if not set<br>
example:<br>
`innerSeams=true`
---
## `weights`
Priority, used for `Random` type to determine which textures have certain chances to be used for render<br>
accepts several values, assigns values correspondingly to `tiles` list values
examples:<br>
`weight=10`<br>
`weights=1 3 3 2 1`
---
# `Method`
Selects CTM mode:
- [LINK](#full-ctm-ctm) `ctm` / `glass` / `default`
- [LINK](#compact-ctm) `compact` / `ctm_compact`
- [LINK](#horizontal)`horizontal` / `bookshelf`
- [LINK](#vertical)`vertical`
- [LINK](#horizontalvertical-hv)`horizontal+vertical` / `h+v`
- [LINK](#verticalhorizontal-vh)`vertical+horizontal` / `v+h`
- [LINK](#top)`top` / `sandstone`
- [LINK](#repeat)`repeat` / `pattern`
- [LINK](#random)`random`
- [LINK](#fixed)`fixed` / `static`
example:<br>
`method=ctm`
### Full CTM (ctm)
Requires exactly 47 tiles, the most versatile but the most size-heavy method
Here's a visual explanation of how the textures should be numbered:
<img src="img/CTM/CTM-pattern.png" width="624" style="image-rendering: pixelated;">
---
### Compact CTM
Requires exactly 5 tiles.
<img src="img/CTM/CCTM-pattern.png" width="284" style="image-rendering: pixelated;">
Splits face into 4 quadrants and builds final texture per quadrant.
Here's a visual explanation of how it works on an example of 3x3 blocks pattern with a cutout:
<img src="img/CTM/CCTM-principle.png" width="400" style="image-rendering: pixelated;">
as you can see, compact CTM has a single limitation:<br>
the size of any part of texture, such as an edge or a corner, can't be larger than `8x8` pixel<br>
this is the main case where only full CTM can be used.
---
### Horizontal
Requires exactly 4 tiles. connects only horizontally, used for bookshelves for example
<img src="img/CTM/HCTM-pattern.png" width="284" style="image-rendering: pixelated;">
---
### Vertical
Requires exactly 4 tiles.
<img src="img/CTM/VCTM-pattern.png" height="284" style="image-rendering: pixelated;">
---
### Horizontal+Vertical (h+v)
Requires exactly 7 tiles.
Two-stage: horizontal first, then vertical if none horizontal variations are found.
<img src="img/CTM/HVCTM-pattern.png" height="284" style="image-rendering: pixelated;">
---
### Vertical+Horizontal (v+h)
Requires exactly 7 tiles.
Two-stage: vertical first, then horizontal if none vertical variations are found.
<img src="img/CTM/VHCTM-pattern.png" height="284" style="image-rendering: pixelated;">
---
### Top
Requires exactly 1 tile.
Applies tile on side faces when block above matches, for example used for smooth sandstone
---
### Repeat
Requires width * height tiles.
Properties:
- `width` - defines the number of tiles in width
- `height` - defines the number of tiles in height
- `symmetry` - defines the symmetry of the pattern, can be either `none` or `opposite`
Just uses N*M grid, for example<br>
`0 1 2 3 4`<br>
`5 6 7 8 9`
---
### Random
Any number of tiles.
Properties:
- `weights` - described [here](#weights)
- `symmetry` - accepts `all`, `opposite`, `none`, groups opposite faces together, otherwise keeps them independent
- `linked` - makes the textures consistent between render states, not needed much
---
### Fixed
Requires exactly 1 tile. Always uses the same tile.<br>
useful only if combined with height or other properties (see examples)
---
# Examples
## Glass via full CTM
```properties
matchBlocks=minecraft:glass minecraft:glass_pane
metadata=0
method=ctm
faces=all
tiles=0-46
connect=block
```
## Multiple properties files usage
### Use multiple .properties files with different heights for more detailed worldgen
this one will use one texture for lower part of the world and another for the rest of the world
```properties
matchBlocks=minecraft:dirt
metadata=0
method=fixed
faces=all
tiles=0
connect=block
maxHeight=40
```
```properties
matchBlocks=minecraft:dirt
metadata=0
method=fixed
faces=all
tiles=0
connect=block
minHeight=41
```
---
+2 -1
View File
@@ -23,7 +23,7 @@ Targets GL 3.3+ core profile
# Required Dependencies
* [UniMixins](https://github.com/LegacyModdingMC/UniMixins/releases) >= 0.1.23
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.9.0
* [GTNHLib](https://github.com/GTNewHorizons/GTNHLib/releases) >= 0.11.19
* NOTE: Some mods are not required, but a specific version is required if present - see: `Permanent Incompatibilities` below.
# Known (temporary) Incompatibilities
@@ -50,6 +50,7 @@ Targets GL 3.3+ core profile
* CodeChicken[Core](https://github.com/GTNewHorizons/CodeChickenCore/releases)/[Lib](https://github.com/GTNewHorizons/CodeChickenLib/releases) <1.2.0 - Threading issues, use 1.3.0+
* Neodymium - Untested, but unlikely to be compatible. (Chunk Meshing is definitely incompatible, other features might work depending on config)
* SmoothFont - Equivalent functionality added
* ClientFixer - Use a resource pack like [GTNH-FTI-Standard-Font](https://github.com/Eldrinn-Elantey/GTNH-FTI-Standard-Font)
# Credits
* ShadersMod
+5
View File
@@ -1,9 +1,14 @@
import xyz.wagyourtail.jvmdg.gradle.flags.DowngradeFlags
import xyz.wagyourtail.jvmdg.gradle.task.DowngradeJar
import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
plugins {
id("com.gtnewhorizons.gtnhconvention")
}
tasks.withType<DowngradeJar>().configureEach { logLevel.set("FATAL") }
tasks.withType<DowngradeFiles>().configureEach { logLevel.set("FATAL") }
minecraft {
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true")
extraRunJvmArguments.add("-Dangelica.dumpClass=true")
+31 -6
View File
@@ -66,9 +66,9 @@ configurations.all {
}
dependencies {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.14:dev")
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.158:dev")
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.7") { transitive = false }
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.1.0") { transitive = false }
// runtimeOnlyNonPublishable("com.github.GTNewHorizons:lwjgl3ify:3.0.99")
compileOnly("org.projectlombok:lombok:1.18.42") { transitive = false }
@@ -76,27 +76,45 @@ dependencies {
// Iris Shaders
compileOnly('org.jetbrains:annotations:26.0.2')
api("com.github.GTNewHorizons:GTNHLib:0.9.36:dev")
api("com.github.GTNewHorizons:GTNHLib:0.11.19:dev")
shadowImplementation(project(":glsm")) { transitive = false }
shadowImplementation(project(":lwjgl3-backend")) { transitive = false }
shadowImplementation("net.minecraftforge:eventbus:7.0-beta.15")
shadowImplementation("org.anarres:jcpp:1.4.14") // Apache 2.0
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-32.g7dd88a4-GTNH") {
shadowImplementation("org.taumc:glsl-transformation-lib:0.2.0-33.g3ec381e") {
exclude module: "antlr4" // we only want to shadow the runtime module
}
shadowImplementation("org.antlr:antlr4-runtime:4.13.2")
compileOnly "org.apache.ant:ant:1.8.2"
// Celeritas (transitive=false: deps provided by GTNHLib/runtime)
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.4-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-common:2.5.5-GTNH") { transitive = false }
embedOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:2.5.3-GTNH") { transitive = false }
// Because who doesn't want NEI
devOnlyNonPublishable('com.github.GTNewHorizons:NotEnoughItems:2.8.35-GTNH:dev')
devOnlyNonPublishable('com.github.GTNewHorizons:CodeChickenCore:1.4.7:dev')
// Display List Testing
/*
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-beta-749-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ae2stuff:0.10.14-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BuildCraft:7.1.54:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:IguanaTweaksTConstruct:2.7.0:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenBlocks:1.12.4-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenComputers:1.12.12-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:TinkersConstruct:1.14.12-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ServerUtilities:2.2.13:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ironchest:6.1.6:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:waila:1.9.7:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Botania:1.13.15-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Mobs-Info:0.5.10-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Draconic-Evolution:1.5.18-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:HoloInventory:2.5.8-GTNH:dev")
*/
//devOnlyNonPublishable('com.github.GTNewHorizons:worldedit-gtnh:v0.0.9')
// Notfine Deps
@@ -126,9 +144,11 @@ dependencies {
compileOnly(rfg.deobf("curse.maven:campfirebackport-387444:4611675"))
compileOnly(rfg.deobf("curse.maven:xaeros-minimap-263420:5060684"))
compileOnly(rfg.deobf("curse.maven:security-craft-64760:2818228"))
compileOnly(rfg.deobf("curse.maven:et-futurum-requiem-441392:5873837"))
compileOnly("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.49-GTNH:dev")
compileOnly(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
devOnlyNonPublishable("com.github.GTNewHorizons:CosmeticArmorReworked:1.0.3-GTNH:dev")
// For testing alternative splash screens — https://github.com/MalTeeez/ModernSplash/releases
// devOnlyNonPublishable("com.github.MalTeeez:modernsplash:1.7.10-1.2.4:dev")
@@ -153,8 +173,13 @@ dependencies {
testImplementation('org.junit.platform:junit-platform-launcher')
testImplementation('com.tngtech.archunit:archunit-junit5:1.4.1')
testRuntimeOnly("it.unimi.dsi:fastutil:8.5.18")
testRuntimeOnly("org.joml:joml:1.10.8")
}
}
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
compileOnly("com.cardinalstar.cubicchunks:CubicChunks1710:v0.1.5-alpha:dev") { transitive = false }
}
+22
View File
@@ -66,6 +66,7 @@ repositories {
content { includeGroup("xyz.wagyourtail.jvmdowngrader") }
}
mavenCentral()
mavenLocal()
}
dependencies {
@@ -95,6 +96,8 @@ dependencies {
// Deps (normally from MC) - compile only, but needed at test runtime
compileOnly("com.google.guava:guava:${property("guavaVersion")}")
testRuntimeOnly("com.google.guava:guava:${property("guavaVersion")}")
testRuntimeOnly("it.unimi.dsi:fastutil:${property("fastutilVersion")}")
testRuntimeOnly("org.joml:joml:${property("jomlVersion")}") { isTransitive = false }
// Test
testImplementation(sourceSets["stubs"].output)
@@ -123,6 +126,7 @@ tasks.withType<DowngradeFiles>().configureEach {
downgradeTo.set(JavaVersion.VERSION_1_8)
multiReleaseOriginal.set(false)
multiReleaseVersions.set(emptySet())
logLevel.set("FATAL")
}
val downgradeDepsForTest by tasks.registering(DowngradeFiles::class) {
@@ -138,6 +142,7 @@ val downgradeMainClasses by tasks.registering(DowngradeFiles::class) {
val downgradeTestClasses by tasks.registering(DowngradeFiles::class) {
inputCollection = sourceSets["test"].output.classesDirs
classpath = sourceSets["test"].compileClasspath
sourceSets["test"].output.classesDirs.files.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
dependsOn(tasks.named("testClasses"))
}
@@ -146,6 +151,10 @@ tasks.test {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(8)
if (System.getProperty("os.name").lowercase().contains("mac")
&& System.getProperty("os.arch") == "aarch64") {
vendor = JvmVendorSpec.AZUL
}
}
dependsOn(downgradeMainClasses, downgradeTestClasses, downgradeDepsForTest)
@@ -169,6 +178,19 @@ tasks.test {
jvmArgs("-Djava.library.path=${extractNatives.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}")
}
val verifyTestsRan by tasks.registering {
val resultsDir = layout.buildDirectory.dir("test-results/test")
dependsOn(tasks.test)
doLast {
val dir = resultsDir.get().asFile
val xmls = dir.listFiles { f -> f.name.startsWith("TEST-") && f.name.endsWith(".xml") } ?: emptyArray()
check(xmls.isNotEmpty()) {
":glsm:test produced no TEST-*.xml in $dir - test task likely went NO-SOURCE"
}
}
}
tasks.check { dependsOn(verifyTestsRan) }
publishing {
publications {
create<MavenPublication>("mavenJava") {
+3 -1
View File
@@ -4,7 +4,9 @@ celeritasVersion = 2.5.3-GTNH
lombokVersion = 1.18.42
log4jVersion = 2.0-beta9
guavaVersion = 18.0
gtnhlibVersion = 0.9.36
gtnhlibVersion = 0.11.19
fastutilVersion = 8.5.18
jomlVersion = 1.10.8
glslTransformLibVersion = 0.2.0-26.g5ee7d96-GTNH
antlr4RuntimeVersion = 4.13.2
jcppVersion = 1.4.14
@@ -1,67 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.AccumulatedDraw;
import com.gtnewhorizons.angelica.glsm.recording.CommandBuffer;
import com.gtnewhorizons.angelica.glsm.recording.CommandBufferProcessor;
import java.util.List;
/**
* Builds final command buffers from raw command buffers and compiled VBO data.
* Bridges the gap between VBO compilation (glsm types) and buffer processing (recording types).
*/
public final class CommandBufferBuilder {
private CommandBufferBuilder() {}
/**
* Build commands with transform collapsing optimization.
* Accumulates MODELVIEW transforms and emits MultMatrix at barriers (draws, CallList).
* Uses mergedRanges where consecutive same-transform draws are combined.
*/
public static void buildFromRawBuffer(
CommandBuffer rawBuffer,
List<AccumulatedDraw> accumulatedDraws,
CommandBuffer finalBuffer) {
rawBuffer.resetRead();
int cmdIndex = 0;
int rangeIndex = 0;
AccumulatedDraw draw;
while (rawBuffer.hasRemaining()) {
// Read and process the command
CommandBufferProcessor.copyCommand(rawBuffer, finalBuffer);
// Emit draw ranges at this command position
while (rangeIndex < accumulatedDraws.size() && (draw = accumulatedDraws.get(rangeIndex)).commandIndex == cmdIndex) {
emitDrawRangeToBuffer(draw, finalBuffer, rangeIndex);
rangeIndex++;
}
cmdIndex++;
}
while (rangeIndex < accumulatedDraws.size()) {
emitDrawRangeToBuffer(accumulatedDraws.get(rangeIndex), finalBuffer, rangeIndex);
rangeIndex++;
}
}
/**
* Emit a draw range to the output buffer, with transform if needed.
*/
private static void emitDrawRangeToBuffer(
AccumulatedDraw draw,
CommandBuffer out,
int vboIdx) {
if (draw.restoreData != null) {
out.writeDrawRangeRestore(vboIdx, draw.restoreData);
return;
}
// Write the draw range command
out.writeDrawRange(vboIdx);
}
}
@@ -1,6 +1,10 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.ShaderParser;
import org.taumc.glsl.Transformer;
@@ -9,9 +13,11 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
@@ -114,13 +120,17 @@ public class CompatShaderTransformer {
return result;
}
private static boolean needsTransformation(String source) {
// Shaders already at 330+ core don't need compat transformation
public static boolean isCoreShader(String source) {
final Matcher vm = VERSION_PATTERN.matcher(source);
if (vm.find()) {
final int version = Integer.parseInt(vm.group(1));
if (version >= 330 && "core".equals(vm.group(2))) return false;
return version >= 330 && "core".equals(vm.group(2));
}
return false;
}
private static boolean needsTransformation(String source) {
if (isCoreShader(source)) return false;
return NEEDS_TRANSFORM_PATTERN.matcher(source).find();
}
@@ -179,8 +189,9 @@ public class CompatShaderTransformer {
transformer.rename("gl_FrontColor", "angelica_FrontColor");
transformer.prependMain("angelica_FrontColor = vec4(1.0);");
// Vertex attributes — replaces removed FFP vertex inputs with explicit in declarations
transformVertexAttributes(transformer, source);
final FFPInputs ffp = FFPInputs.detect(source);
assignVertexInputLocations(transformer, ffp);
transformVertexAttributes(transformer, source, ffp);
} else {
if (source.contains("gl_Color")) {
transformer.injectVariable("in vec4 angelica_FrontColor;");
@@ -307,25 +318,25 @@ public class CompatShaderTransformer {
/**
* Replace removed FFP vertex attributes with explicit {@code in} declarations at core profile attribute locations.
*/
private static void transformVertexAttributes(Transformer transformer, String source) {
if (source.contains("gl_Vertex") || source.contains("ftransform")) {
private static void transformVertexAttributes(Transformer transformer, String source, FFPInputs ffp) {
if (ffp.position()) {
transformer.injectVariable("layout(location = 0) in vec4 angelica_Vertex;");
transformer.rename("gl_Vertex", "angelica_Vertex");
}
// gl_Color in vertex shaders is the per-vertex color attribute, distinct from the fragment gl_Color (interpolated gl_FrontColor) handled above
if (source.contains("gl_Color")) {
if (ffp.color()) {
transformer.injectVariable("layout(location = 1) in vec4 angelica_Color;");
transformer.rename("gl_Color", "angelica_Color");
}
if (source.contains("gl_MultiTexCoord0")) {
if (ffp.uv0()) {
transformer.injectVariable("layout(location = 2) in vec4 angelica_MultiTexCoord0;");
transformer.rename("gl_MultiTexCoord0", "angelica_MultiTexCoord0");
}
if (source.contains("gl_MultiTexCoord1")) {
if (ffp.uv1()) {
transformer.injectVariable("layout(location = 3) in vec4 angelica_MultiTexCoord1;");
transformer.rename("gl_MultiTexCoord1", "angelica_MultiTexCoord1");
}
if (source.contains("gl_Normal")) {
if (ffp.normal()) {
transformer.injectVariable("layout(location = 4) in vec3 angelica_Normal;");
transformer.rename("gl_Normal", "angelica_Normal");
}
@@ -334,6 +345,145 @@ public class CompatShaderTransformer {
}
}
private static final int MAX_VERTEX_ATTRIBS = 16;
private record FFPInputs(boolean position, boolean color, boolean uv0, boolean uv1, boolean normal) {
static FFPInputs detect(String source) {
return new FFPInputs(
source.contains("gl_Vertex") || source.contains("ftransform"),
source.contains("gl_Color"),
source.contains("gl_MultiTexCoord0"),
source.contains("gl_MultiTexCoord1"),
source.contains("gl_Normal"));
}
}
/** Adaptation of Mesa's assign_attribute_or_color_locations */
private static void assignVertexInputLocations(Transformer transformer, FFPInputs ffp) {
int used = 0;
if (ffp.position()) used |= 1 << Usage.POSITION.getAttributeLocation();
if (ffp.color()) used |= 1 << Usage.COLOR.getAttributeLocation();
if (ffp.uv0()) used |= 1 << Usage.PRIMARY_UV.getAttributeLocation();
if (ffp.uv1()) used |= 1 << Usage.SECONDARY_UV.getAttributeLocation();
if (ffp.normal()) used |= 1 << Usage.NORMAL.getAttributeLocation();
record Input(String name, String baseType, String arraySuffix, int slots, int index) {}
final List<Input> toAssign = new ArrayList<>();
int index = 0;
for (TerminalNode storage : transformer.collectStorage()) {
final int tokenType = storage.getSymbol().getType();
if (tokenType != GLSLLexer.ATTRIBUTE && tokenType != GLSLLexer.IN) continue;
ParseTree node = storage.getParent();
while (node != null && !(node instanceof GLSLParser.Single_declarationContext)) {
node = node.getParent();
}
if (!(node instanceof GLSLParser.Single_declarationContext decl)) continue;
if (decl.typeless_declaration() == null) continue;
final GLSLParser.Type_specifierContext typeSpec = decl.fully_specified_type().type_specifier();
final String baseType = GlslTransformUtils.getFormattedShader(typeSpec.type_specifier_nonarray(), "").trim();
final int typeArraySlots = arraySlots(typeSpec.array_specifier());
final String typeArray = arraySuffix(typeSpec.array_specifier());
if (hasLayoutQualifier(decl)) {
final int loc = explicitLocation(decl);
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(decl.typeless_declaration().array_specifier());
if (loc >= 0) used |= slotMask(slots) << loc;
continue;
}
final List<GLSLParser.Typeless_declarationContext> declarators = new ArrayList<>();
declarators.add(decl.typeless_declaration());
if (decl.getParent() instanceof GLSLParser.Init_declarator_listContext list) {
declarators.addAll(list.typeless_declaration());
}
for (GLSLParser.Typeless_declarationContext d : declarators) {
if (d.IDENTIFIER() == null) continue;
final int slots = baseSlots(baseType) * typeArraySlots * arraySlots(d.array_specifier());
final String arraySuffix = typeArray + arraySuffix(d.array_specifier());
toAssign.add(new Input(d.IDENTIFIER().getText(), baseType, arraySuffix, slots, index++));
}
}
toAssign.sort((a, b) -> a.slots() != b.slots() ? b.slots() - a.slots() : a.index() - b.index());
for (Input in : toAssign) {
final int loc = findAvailableSlots(used, in.slots());
if (loc < 0) continue;
used |= slotMask(in.slots()) << loc;
transformer.removeVariable(in.name());
transformer.variable = null;
transformer.injectVariable("layout(location = " + loc + ") in " + in.baseType() + " " + in.name() + in.arraySuffix() + ";");
}
}
private static String arraySuffix(GLSLParser.Array_specifierContext arraySpec) {
return arraySpec != null ? GlslTransformUtils.getFormattedShader(arraySpec, "").trim() : "";
}
private static int arraySlots(GLSLParser.Array_specifierContext arraySpec) {
if (arraySpec == null) return 1;
int product = 1;
for (GLSLParser.DimensionContext dim : arraySpec.dimension()) {
if (dim.constant_expression() == null) continue;
try {
product *= Integer.decode(GlslTransformUtils.getFormattedShader(dim.constant_expression(), "").trim());
} catch (NumberFormatException ignored) {}
}
return product;
}
private static int baseSlots(String type) {
final String dims = type.startsWith("dmat") ? type.substring(4) : type.startsWith("mat") ? type.substring(3) : null;
if (dims != null && !dims.isEmpty() && Character.isDigit(dims.charAt(0))) {
return dims.charAt(0) - '0';
}
return 1;
}
private static int slotMask(int slots) {
return (slots >= 32) ? -1 : (1 << slots) - 1;
}
private static int findAvailableSlots(int used, int needed) {
if (needed <= 0) return -1;
final int mask = slotMask(needed);
for (int i = 0; i <= MAX_VERTEX_ATTRIBS - needed; i++) {
if (((mask << i) & ~used) == (mask << i)) return i;
}
return -1;
}
private static int explicitLocation(GLSLParser.Single_declarationContext decl) {
final GLSLParser.Type_qualifierContext qualifier = decl.fully_specified_type().type_qualifier();
if (qualifier == null) return -1;
for (GLSLParser.Single_type_qualifierContext single : qualifier.single_type_qualifier()) {
final GLSLParser.Layout_qualifierContext layout = single.layout_qualifier();
if (layout == null) continue;
for (GLSLParser.Layout_qualifier_idContext id : layout.layout_qualifier_id_list().layout_qualifier_id()) {
if (id.IDENTIFIER() != null && "location".equals(id.IDENTIFIER().getText()) && id.constant_expression() != null) {
try {
return Integer.decode(GlslTransformUtils.getFormattedShader(id.constant_expression(), "").trim());
} catch (NumberFormatException ignored) {
return -1;
}
}
}
}
return -1;
}
private static boolean hasLayoutQualifier(GLSLParser.Single_declarationContext decl) {
final GLSLParser.Type_qualifierContext qualifier = decl.fully_specified_type().type_qualifier();
if (qualifier == null) return false;
for (GLSLParser.Single_type_qualifierContext single : qualifier.single_type_qualifier()) {
if (single.layout_qualifier() != null) return true;
}
return false;
}
private static void dumpShader(String original, String transformed, boolean isFragment, boolean wasTransformed) {
if (DUMP_DIR == null) return;
final int id = dumpCounter.getAndIncrement();
@@ -7,18 +7,16 @@ import com.gtnewhorizons.angelica.glsm.states.LightModelState;
import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Per-program compat uniform location cache and upload manager.
*
@@ -61,7 +59,10 @@ public class CompatUniformManager {
static final int MAT_FIELDS = 5;
static final int LOC_MAT_BASE = LOC_LIGHT_BASE + 2 * LIGHT_FIELDS; // 43
static final int LOC_COUNT = LOC_MAT_BASE + MAT_FIELDS; // 48
static final int LOC_IRIS_MODELVIEW_INVERSE = LOC_MAT_BASE + MAT_FIELDS; // 48
static final int LOC_IRIS_PROJECTION_INVERSE = LOC_IRIS_MODELVIEW_INVERSE + 1; // 49
static final int LOC_COUNT = LOC_IRIS_PROJECTION_INVERSE + 1; // 50
private static final String[] LIGHT_FIELD_NAMES = {
"ambient", "diffuse", "specular", "position", "halfVector",
@@ -91,6 +92,8 @@ public class CompatUniformManager {
UNIFORM_NAMES[LOC_IRIS_NORMAL] = "iris_NormalMatrix";
UNIFORM_NAMES[LOC_IRIS_LIGHTMAP_TEXTURE_MATRIX] = "iris_LightmapTextureMatrix";
UNIFORM_NAMES[LOC_IRIS_TEXTURE_MATRIX] = "iris_TextureMatrix";
UNIFORM_NAMES[LOC_IRIS_MODELVIEW_INVERSE] = "iris_ModelViewMatrixInverse";
UNIFORM_NAMES[LOC_IRIS_PROJECTION_INVERSE] = "iris_ProjectionMatrixInverse";
UNIFORM_NAMES[LOC_ALPHA_TEST_REF] = "angelica_currentAlphaTest";
UNIFORM_NAMES[LOC_SCENE_COLOR] = "angelica_SceneColor";
UNIFORM_NAMES[LOC_CLIP_PLANES] = "angelica_ClipPlane[0]";
@@ -105,8 +108,15 @@ public class CompatUniformManager {
}
}
/** Per-program cached uniform locations. Maps program ID → int[LOC_COUNT]. */
private static final Int2ObjectOpenHashMap<int[]> programLocations = new Int2ObjectOpenHashMap<>();
/** Per-program cached uniform locations + last-uploaded generations. Maps program ID -> state. */
private static final Int2ObjectOpenHashMap<ProgramUniforms> programUniforms = new Int2ObjectOpenHashMap<>();
private static final class ProgramUniforms {
final int[] locs;
int lastMvGen = -1, lastMvLinearGen = -1, lastProjGen = -1, lastTexMatGen = -1;
int lastFragmentGen = -1, lastLightingGen = -1, lastClipPlaneGen = -1;
ProgramUniforms(int[] locs) { this.locs = locs; }
}
// Reusable NIO buffers for upload
private static final FloatBuffer mat4Buf = BufferUtils.createFloatBuffer(16);
@@ -115,6 +125,7 @@ public class CompatUniformManager {
private static final FloatBuffer clipPlaneBuf = BufferUtils.createFloatBuffer(32); // 8 planes * vec4
private static final Matrix3f normalMatrix = new Matrix3f();
private static final Matrix4f scratchMatrix = new Matrix4f();
private static final Vector3f scratchCol = new Vector3f();
private static final float LIGHTMAP_SCALE = 1.0f / 256.0f;
private static final FloatBuffer lightmapMatrixBuf;
@@ -124,17 +135,18 @@ public class CompatUniformManager {
new Matrix4f().scale(LIGHTMAP_SCALE).translate(8.0f, 8.0f, 8.0f).get(lightmapMatrixBuf);
}
// Dirty tracking: skip uploads when state hasn't changed and program is the same
private static int lastProgram = -1;
private static int lastMvGen = -1;
private static int lastProjGen = -1;
private static int lastTexMatGen = -1;
private static int lastFragmentGen = -1;
private static int lastLightingGen = -1;
private static int lastClipPlaneGen = -1;
private CompatUniformManager() {}
/**
* Normalize each column of a 3x3 matrix in place. Used to strip the residual 1/scale that
* inverse-transpose introduces.
*/
private static void normalizeColumnsInPlace(Matrix3f m) {
m.setColumn(0, m.getColumn(0, scratchCol).normalize());
m.setColumn(1, m.getColumn(1, scratchCol).normalize());
m.setColumn(2, m.getColumn(2, scratchCol).normalize());
}
public static void onLinkProgram(int program) {
int[] locs = new int[LOC_COUNT];
boolean hasAny = false;
@@ -145,45 +157,49 @@ public class CompatUniformManager {
}
if (hasAny) {
programLocations.put(program, locs);
programUniforms.put(program, new ProgramUniforms(locs));
GLStateManager.LOGGER.debug("CompatUniformManager: program {} has compat uniforms", program);
}
}
public static void onUseProgram(int program) {
if (program == 0) return;
/**
* Uploads the emulated uniforms.
*
* @return Whether the program needs emulation (FFP or Iris Program)
*/
public static boolean refreshCompatUniforms(int program) {
final ProgramUniforms pu = programUniforms.get(program);
if (pu == null) return false;
final int[] locs = pu.locs;
int[] locs = programLocations.get(program);
if (locs == null) return;
final boolean programChanged = program != lastProgram;
lastProgram = program;
// Matrix uniforms — skip if generation unchanged and same program
// Matrix uniforms — skip if this program's storage already holds the current generation
final int mvGen = GLStateManager.mvGeneration;
final int mvLinearGen = GLStateManager.mvLinearGeneration;
final int projGen = GLStateManager.projGeneration;
final int texMatGen = GLStateManager.texMatrixGeneration;
final boolean mvChanged = programChanged || mvGen != lastMvGen;
final boolean projChanged = programChanged || projGen != lastProjGen;
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
final boolean mvChanged = mvGen != pu.lastMvGen;
final boolean mvLinearChanged = mvLinearGen != pu.lastMvLinearGen;
final boolean projChanged = projGen != pu.lastProjGen;
final boolean texMatChanged = texMatGen != pu.lastTexMatGen;
if (mvChanged || projChanged || texMatChanged) {
uploadMatrices(locs, mvChanged, projChanged, texMatChanged);
lastMvGen = mvGen;
lastProjGen = projGen;
lastTexMatGen = texMatGen;
uploadMatrices(locs, mvChanged, mvLinearChanged, projChanged, texMatChanged);
pu.lastMvGen = mvGen;
pu.lastMvLinearGen = mvLinearGen;
pu.lastProjGen = projGen;
pu.lastTexMatGen = texMatGen;
}
// Fragment-category uniforms (fog, alpha) — skip if generation unchanged
final int fragGen = GLStateManager.fragmentGeneration;
if (programChanged || fragGen != lastFragmentGen) {
lastFragmentGen = fragGen;
if (fragGen != pu.lastFragmentGen) {
pu.lastFragmentGen = fragGen;
uploadFragmentUniforms(locs);
}
// Lighting-derived uniforms (scene color, light sources, material)
final int litGen = GLStateManager.lightingGeneration;
if (programChanged || litGen != lastLightingGen) {
lastLightingGen = litGen;
if (litGen != pu.lastLightingGen) {
pu.lastLightingGen = litGen;
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
uploadLightSources(locs);
uploadMaterial(locs);
@@ -192,14 +208,16 @@ public class CompatUniformManager {
// Clip plane equations + enabled bool — uploaded when enable state or equations change
if (locs[LOC_CLIP_PLANES] != -1 || locs[LOC_CLIP_PLANES_ENABLED] != -1) {
final int cpGen = GLStateManager.clipPlaneGeneration;
if (programChanged || cpGen != lastClipPlaneGen) {
lastClipPlaneGen = cpGen;
if (cpGen != pu.lastClipPlaneGen) {
pu.lastClipPlaneGen = cpGen;
uploadClipPlanes(locs);
}
}
return true;
}
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
private static void uploadMatrices(int[] locs, boolean mvChanged, boolean mvLinearChanged, boolean projChanged, boolean texMatChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
@@ -216,20 +234,30 @@ public class CompatUniformManager {
}
// ModelView Inverse
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
if (locs[LOC_MODELVIEW_INVERSE] != -1 || locs[LOC_IRIS_MODELVIEW_INVERSE] != -1) {
mv.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
if (locs[LOC_MODELVIEW_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_MODELVIEW_INVERSE], false, mat4Buf);
}
if (locs[LOC_IRIS_MODELVIEW_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_MODELVIEW_INVERSE], false, mat4Buf);
}
}
}
if (mvLinearChanged) {
// Normal Matrix (inverse-transpose of upper-left 3x3 of ModelView)
if (locs[LOC_NORMAL] != -1 || locs[LOC_IRIS_NORMAL] != -1) {
mv.normal(normalMatrix);
normalMatrix.get(mat3Buf);
if (locs[LOC_NORMAL] != -1) {
normalMatrix.get(mat3Buf);
RENDER_BACKEND.uniformMatrix3(locs[LOC_NORMAL], false, mat3Buf);
}
// Column-normalized to unit magnitude
if (locs[LOC_IRIS_NORMAL] != -1) {
normalizeColumnsInPlace(normalMatrix);
normalMatrix.get(mat3Buf);
RENDER_BACKEND.uniformMatrix3(locs[LOC_IRIS_NORMAL], false, mat3Buf);
}
}
@@ -248,10 +276,15 @@ public class CompatUniformManager {
}
// Projection Inverse
if (locs[LOC_PROJECTION_INVERSE] != -1) {
if (locs[LOC_PROJECTION_INVERSE] != -1 || locs[LOC_IRIS_PROJECTION_INVERSE] != -1) {
proj.invert(scratchMatrix);
scratchMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
if (locs[LOC_PROJECTION_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_PROJECTION_INVERSE], false, mat4Buf);
}
if (locs[LOC_IRIS_PROJECTION_INVERSE] != -1) {
RENDER_BACKEND.uniformMatrix4(locs[LOC_IRIS_PROJECTION_INVERSE], false, mat4Buf);
}
}
}
@@ -401,14 +434,15 @@ public class CompatUniformManager {
}
public static void onDeleteProgram(int program) {
programLocations.remove(program);
programUniforms.remove(program);
}
public static boolean hasProgram(int program) {
return programLocations.containsKey(program);
return programUniforms.containsKey(program);
}
public static int[] getLocations(int program) {
return programLocations.get(program);
final ProgramUniforms pu = programUniforms.get(program);
return pu == null ? null : pu.locs;
}
}
File diff suppressed because it is too large. Load diff
@@ -1,7 +1,7 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import org.joml.Matrix4f;
import org.joml.Vector4f;
@@ -9,13 +9,13 @@ import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Software emulation of GL feedback mode
*/
@@ -127,12 +127,12 @@ public class FeedbackManager {
}
private static int positionStride() {
final int s = VertexAttribState.get(0).effectiveStride();
final int s = VAOManager.getAttribEffectiveStride(0);
return (s != 0) ? s : 12;
}
private static long positionOffset() {
return VertexAttribState.get(0).offset;
return VAOManager.getAttribOffset(0);
}
public static void processDrawArrays(int mode, int first, int count) {
@@ -142,7 +142,7 @@ public class FeedbackManager {
final int byteCount = count * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) first * stride + posOffset, readbackBuf);
readPositionBuffer((long) first * stride + posOffset, readbackBuf);
emitAllPrimitives(mode, count, i -> i, stride);
}
@@ -151,7 +151,7 @@ public class FeedbackManager {
computeMVP();
final int stride = positionStride();
final int elementSize = VertexAttribState.Attrib.glTypeSizeBytes(type);
final int elementSize = VAOManager.Attrib.glTypeSizeBytes(type);
final int indexByteCount = indexCount * elementSize;
ensureIndexReadbackCapacity(indexByteCount);
indexReadbackBuf.clear().limit(indexByteCount);
@@ -170,7 +170,7 @@ public class FeedbackManager {
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf);
final int base = minIdx;
emitAllPrimitives(mode, indexCount, i -> ImmediateModeRecorder.readIndex(indexBuf, type, 0, i) - base, stride);
@@ -251,7 +251,23 @@ public class FeedbackManager {
final int byteCount = vertexCount * stride;
ensureReadbackCapacity(byteCount);
readbackBuf.clear().limit(byteCount);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, (long) minIdx * stride + posOffset, readbackBuf);
readPositionBuffer((long) minIdx * stride + posOffset, readbackBuf);
}
private static void readPositionBuffer(long offset, ByteBuffer destination) {
final int positionVbo = VAOManager.getAttribVBO(0);
final int previousVbo = GLStateManager.getBoundVBO();
if (positionVbo != 0 && positionVbo != previousVbo) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, positionVbo);
}
try {
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, offset, destination);
} finally {
if (positionVbo != 0 && positionVbo != previousVbo) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, previousVbo);
}
}
}
private static void emitVertex(int vertexIndex, int stride) {
File diff suppressed because it is too large. Load diff
@@ -1,22 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
/**
* Tessellator data needed by {@link com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer}.
*/
public interface ITessellatorData {
boolean isDrawing();
void setDrawing(boolean drawing);
int getVertexCount();
int[] getRawBuffer();
int getRawBufferIndex();
int getRawBufferSize();
void setRawBufferSize(int size);
void setRawBuffer(int[] buffer);
int getDrawMode();
boolean hasTexture();
boolean hasColor();
boolean hasNormals();
boolean hasBrightness();
void angelica$reset();
}
@@ -1,6 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
@@ -9,7 +9,6 @@ import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetByte;
@@ -17,6 +16,7 @@ import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetShort;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutShort;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Shared quad-to-triangle EBO for core profile GL_QUADS emulation.
@@ -83,16 +83,34 @@ public final class QuadConverter {
* @param vertexCount number of vertices (must be multiple of 4)
*/
public static void drawQuadsAsTriangles(int first, int vertexCount) {
assert first % 4 == 0 : "QuadConverter: first (" + first + ") must be a multiple of 4";
assert vertexCount % 4 == 0 : "QuadConverter: vertexCount (" + vertexCount + ") must be a multiple of 4";
final int quadCount = vertexCount / 4;
final int prevEbo = GLStateManager.getBoundEBO();
ensureCapacity(first / 4 + quadCount);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
final long indexOffset = (long) (first / 4) * 6 * 4;
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
drawSharedQuadEbo(first, vertexCount, 1, false);
}
public static void drawQuadsAsTrianglesInstanced(int first, int vertexCount, int primcount) {
drawSharedQuadEbo(first, vertexCount, primcount, true);
}
private static void drawSharedQuadEbo(int first, int vertexCount, int primcount, boolean instanced) {
final boolean locked = GLStateManager.acquireDrawLock();
try {
GLStateManager.preDraw();
assert first % 4 == 0 : "QuadConverter: first (" + first + ") must be a multiple of 4";
assert vertexCount % 4 == 0 : "QuadConverter: vertexCount (" + vertexCount + ") must be a multiple of 4";
final int quadCount = vertexCount / 4;
final int prevEbo = GLStateManager.getBoundEBO();
ensureCapacity(first / 4 + quadCount);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
// Index offset: first vertex / 4 quads * 6 indices * 4 bytes per int
final long indexOffset = (long) (first / 4) * 6 * 4;
if (instanced) {
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset, primcount);
} else {
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, quadCount * 6, INDEX_TYPE, indexOffset);
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
}
/**
@@ -106,27 +124,41 @@ public final class QuadConverter {
* @param bytesPerIndex bytes per index element
*/
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex) {
final int needed = triIndexCount * bytesPerIndex;
final int prevEbo = GLStateManager.getBoundEBO();
uploadAndDraw(dst, triIndexCount, indexType, bytesPerIndex, 1, false);
}
if (scratchEboId == 0) {
scratchEboId = RENDER_BACKEND.genBuffers();
private static void uploadAndDraw(ByteBuffer dst, int triIndexCount, int indexType, int bytesPerIndex, int primcount, boolean instanced) {
final boolean locked = GLStateManager.acquireDrawLock();
try {
GLStateManager.preDraw();
final int needed = triIndexCount * bytesPerIndex;
final int prevEbo = GLStateManager.getBoundEBO();
if (scratchEboId == 0) {
scratchEboId = RENDER_BACKEND.genBuffers();
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
if (needed > scratchEboCapacity) {
// Power-of-2 growth -- allocate full capacity, upload actual data
int newCap = Math.max(4096, scratchEboCapacity);
while (newCap < needed) newCap *= 2;
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
scratchEboCapacity = newCap;
}
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
if (instanced) {
RENDER_BACKEND.drawElementsInstanced(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L, primcount);
} else {
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, scratchEboId);
if (needed > scratchEboCapacity) {
// Power-of-2 growth — allocate full capacity, upload actual data
int newCap = Math.max(4096, scratchEboCapacity);
while (newCap < needed) newCap *= 2;
RENDER_BACKEND.bufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, newCap, GL15.GL_STREAM_DRAW);
scratchEboCapacity = newCap;
}
RENDER_BACKEND.bufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, 0, dst);
RENDER_BACKEND.drawElements(GL11.GL_TRIANGLES, triIndexCount, indexType, 0L);
RENDER_BACKEND.bindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEbo);
memFree(dst);
}
@@ -139,12 +171,20 @@ public final class QuadConverter {
* @param offset byte offset into the currently bound EBO
*/
public static void drawQuadElementsAsTriangles(int indexCount, int type, long offset) {
drawQuadElementsFromEbo(indexCount, type, offset, 1, false);
}
public static void drawQuadElementsAsTrianglesInstanced(int indexCount, int type, long offset, int primcount) {
drawQuadElementsFromEbo(indexCount, type, offset, primcount, true);
}
private static void drawQuadElementsFromEbo(int indexCount, int type, long offset, int primcount, boolean instanced) {
if (indexCount == 0) return;
assert indexCount % 4 == 0 : "QuadConverter: indexCount must be multiple of 4";
final int quadCount = indexCount / 4;
final int triIndexCount = quadCount * 6;
final int bytesPerIndex = VertexAttribState.Attrib.glTypeSizeBytes(type);
final int bytesPerIndex = VAOManager.Attrib.glTypeSizeBytes(type);
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
@@ -153,7 +193,7 @@ public final class QuadConverter {
triangulateQuads(memAddress0(src), type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
memFree(src);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
}
/**
@@ -8,6 +8,7 @@ import com.gtnewhorizons.angelica.glsm.dsa.DSAEXT;
import com.gtnewhorizons.angelica.glsm.dsa.DSAUnsupported;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import com.gtnewhorizons.angelica.glsm.texture.TextureInfoCache;
import lombok.Getter;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Vector3i;
@@ -47,6 +48,9 @@ public class RenderSystem {
private static int maxSSBOBindings;
private static int maxGlslVersion;
private static boolean supportsGpuShader4;
@Getter private static int glMajor;
@Getter private static int glMinor;
@Getter private static boolean coreProfile;
// Sampler object state tracking (null if unsupported)
private static int[] samplers;
@@ -61,7 +65,13 @@ public class RenderSystem {
if (rendererInitialized) return;
rendererInitialized = true;
try {
if (GLStateManager.capabilities.OpenGL45) {
if (GLStateManager.vendorIsIntel() && GLStateManager.isWindows()) {
dsaState = new DSAUnsupported();
GLStateManager.LOGGER.info("Detected Intel drivers on Windows, disabling DSA.");
} else if (!GLStateManager.getInitConfig().isDSAEnabled()) {
dsaState = new DSAUnsupported();
GLStateManager.LOGGER.info("enableDSA is set to false, disabling DSA.");
} else if (GLStateManager.capabilities.OpenGL45) {
dsaState = (Runtime.version().feature() > 8 && GLStateManager.capabilities.GL_EXT_direct_state_access) ? new DSAEXT() : new DSACore();
GLStateManager.LOGGER.info("OpenGL 4.5 detected, enabling DSA.");
}
@@ -133,10 +143,20 @@ public class RenderSystem {
if (GLStateManager.capabilities.OpenGL32) {
final int profileMask = RENDER_BACKEND.getInteger(GL32.GL_CONTEXT_PROFILE_MASK);
if ((profileMask & GL32.GL_CONTEXT_CORE_PROFILE_BIT) != 0) {
coreProfile = true;
GLStateManager.LOGGER.info("GL 3.3 core profile detected, enabling FFP shader emulation.");
ShaderManager.getInstance().enable();
} else {
throw new IllegalStateException("Non-core GL context (profile mask 0x" + Integer.toHexString(profileMask)
+ "); FFP emulation requires a core profile and nothing would render. Context creation should have rejected this.");
}
}
final Matcher glVerMatcher = SEMVER_PATTERN.matcher(RENDER_BACKEND.getString(GL11.GL_VERSION));
if (glVerMatcher.matches()) {
glMajor = Integer.parseInt(glVerMatcher.group("major"));
glMinor = Integer.parseInt(glVerMatcher.group("minor"));
}
}
public static void generateMipmaps(int texture, int mipmapTarget) {
@@ -149,7 +169,9 @@ public class RenderSystem {
public static void texImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
GLStateManager.glBindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage2D(target, level, internalformat, width, height, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
if (target == GL11.GL_TEXTURE_2D && level == 0) {
TextureInfoCache.INSTANCE.onTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
}
@@ -218,29 +240,41 @@ public class RenderSystem {
}
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, ByteBuffer pixels) {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void textureImage2D(int texture, int target, int level, int internalformat, int width, int height, int border, int format, int type, IntBuffer pixels) {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureImage2D(texture, target, level, internalformat, width, height, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer pixels) {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void textureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, IntBuffer pixels) {
GLStateManager.suspendPixelUnpackBuffer();
dsaState.textureSubImage2D(texture, target, level, xoffset, yoffset, width, height, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void texImage1D(int texture, int target, int level, int internalformat, int width, int border, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.bindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage1D(target, level, internalformat, width, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static void texImage3D(int texture, int target, int level, int internalformat, int width, int height, int depth, int border, int format, int type, ByteBuffer pixels) {
RENDER_BACKEND.bindTexture(target, texture);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texImage3D(target, level, internalformat, width, height, depth, border, format, type, pixels);
GLStateManager.restorePixelUnpackBuffer();
}
public static String getProgramInfoLog(int program) {
@@ -264,11 +298,13 @@ public class RenderSystem {
}
public static void readPixels(int x, int y, int width, int height, int format, int type, FloatBuffer pixels) {
GLStateManager.suspendPixelPackBuffer();
RENDER_BACKEND.readPixels(x, y, width, height, format, type, pixels);
GLStateManager.restorePixelPackBuffer();
}
public static void bufferData(int target, FloatBuffer data, int usage) {
RENDER_BACKEND.bufferData(target, data, usage);
GLStateManager.glBufferData(target, data, usage);
}
public static int bufferStorage(int target, FloatBuffer data, int usage) {
@@ -414,7 +450,7 @@ public class RenderSystem {
}
public static int createBuffers() {
return RENDER_BACKEND.genBuffers();
return GLStateManager.glGenBuffers();
}
public static void bindBufferBase(int target, int index, int buffer) {
@@ -422,13 +458,13 @@ public class RenderSystem {
}
public static void bufferStorage(int target, long size, int flags) {
RENDER_BACKEND.bufferStorage(target, size, flags);
GLStateManager.glBufferStorage(target, size, flags);
}
public static void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, int[] data) {
final ByteBuffer buf = BufferUtils.createByteBuffer(data.length * 4);
buf.asIntBuffer().put(data);
RENDER_BACKEND.clearBufferSubData(target, internalFormat, offset, size, format, type, buf);
GLStateManager.glClearBufferSubData(target, internalFormat, offset, size, format, type, buf);
}
public static void deleteBuffers(int buffer) {
@@ -516,7 +552,7 @@ public class RenderSystem {
}
}
/** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460". Inlined from StandardMacros.getGlVersion(). */
/** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460" */
static String parseGlVersionString(String info) {
final Matcher matcher = SEMVER_PATTERN.matcher(Objects.requireNonNull(info));
if (!matcher.matches()) {
@@ -528,4 +564,18 @@ public class RenderSystem {
if (bugfix == null || bugfix.isEmpty()) bugfix = "0";
return major + minor + bugfix;
}
/** Parse a GL version string into packed major*10+minor (e.g. "4.3.0 - Build ..." -> 43), or -1 if unparseable. */
public static int parseMajorMinor(String glVersion) {
if (glVersion == null) return -1;
final Matcher matcher = SEMVER_PATTERN.matcher(glVersion.trim());
if (!matcher.matches()) return -1;
return Integer.parseInt(matcher.group("major")) * 10 + Integer.parseInt(matcher.group("minor"));
}
public static boolean isContextValid(int requestedMajor, int requestedMinor, int actualVersion, boolean coreBitSet) {
if (!coreBitSet) return false;
if (actualVersion < 0) return true;
return actualVersion >= requestedMajor * 10 + requestedMinor;
}
}
@@ -1,132 +0,0 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.MultMatrixCmd;
import org.joml.Matrix4f;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;
/**
* Helper class for tracking and collapsing matrix transforms during optimization.
* Implements the transform accumulation and emission strategy:
* <ul>
* <li>accumulated: total relative transform (what GL matrix should be)</li>
* <li>lastEmitted: what we've emitted (what GL matrix is)</li>
* <li>stack: saved accumulated values at Push points</li>
* </ul>
*/
class TransformOptimizer {
private final Matrix4f accumulated = new Matrix4f(); // Current accumulated transform
private final Matrix4f lastEmitted = new Matrix4f(); // Last emitted transform
private final Deque<Matrix4f> stack = new ArrayDeque<>(); // For Push/Pop
private final int listId; // For logging
private boolean absoluteMatrixFlag = false; // True after LoadMatrix, cleared by LoadIdentity emit
TransformOptimizer(int listId) {
this.listId = listId;
accumulated.identity();
lastEmitted.identity();
}
Matrix4f getAccumulated() {
return accumulated;
}
void loadIdentity() {
accumulated.identity();
}
void pushTransform() {
stack.push(new Matrix4f(accumulated));
}
void popTransform() {
if (!stack.isEmpty()) {
final Matrix4f popped = stack.pop();
accumulated.set(popped);
// After Pop, GL state is restored to the pushed value so lastEmitted should also reflect this
lastEmitted.set(accumulated);
} else {
GLStateManager.LOGGER.warn("[TransformOptimizer] list={} Pop with empty stack - resetting to identity", listId);
accumulated.identity();
lastEmitted.identity();
}
}
boolean isIdentity() {
return DisplayListManager.isIdentity(accumulated);
}
/**
* Check if there's a pending transform that hasn't been emitted yet.
*/
boolean hasPendingTransform() {
return !accumulated.equals(lastEmitted);
}
/**
* Emit a MultMatrix command if accumulated differs from lastEmitted.
* Updates lastEmitted to match accumulated after emission.
*/
void emitPendingTransform(List<DisplayListCommand> output) {
if (!accumulated.equals(lastEmitted)) {
emitTransformTo(output, accumulated);
}
}
/**
* Emit a MultMatrix command to make GL state match a target transform.
* Used when a draw expects a specific transform that may differ from accumulated.
* Updates lastEmitted to match target after emission.
*
* @param output The command list to emit to
* @param target The target transform to reach
*/
void emitTransformTo(List<DisplayListCommand> output, Matrix4f target) {
if (target.equals(lastEmitted)) {
// Already at target - nothing to emit
return;
}
// Calculate delta: what we need to multiply to go from lastEmitted to target
// delta = inverse(lastEmitted) * target
// But for simplicity, if lastEmitted is identity, just emit target
if (DisplayListManager.isIdentity(lastEmitted)) {
// Simple case: just emit target
output.add(MultMatrixCmd.create(target));
} else {
// Need to emit delta: inv(lastEmitted) * target
final Matrix4f delta = new Matrix4f(lastEmitted).invert().mul(target);
output.add(MultMatrixCmd.create(delta));
}
lastEmitted.set(target);
}
/**
* Check if a draw's expected transform differs from what we've emitted.
*/
boolean needsTransformForDraw(Matrix4f drawTransform) {
return !drawTransform.equals(lastEmitted);
}
/**
* Mark that the GL matrix is at an absolute value (after LoadMatrix).
* Subsequent LoadIdentity commands must be emitted to actually reset GL to identity.
*/
void markAbsoluteMatrix() {
absoluteMatrixFlag = true;
}
/**
* Check if the GL matrix is at an absolute value and clear the flag.
* Used to determine if LoadIdentity needs to emit.
* @return true if LoadMatrix was called and not yet reset by LoadIdentity
*/
boolean checkAndClearAbsoluteMatrix() {
boolean was = absoluteMatrixFlag;
absoluteMatrixFlag = false;
return was;
}
}
@@ -310,6 +310,16 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL31.glDrawElementsInstanced(mode, count, type, indices, primcount);
}
@Override
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
GL31.glDrawArraysInstanced(mode, first, count, primcount);
}
@Override
public void provokingVertex(int provokeMode) {
GL32.glProvokingVertex(provokeMode);
}
@Override
public void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
org.lwjgl.opengl.GL43.glMultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
@@ -317,7 +327,7 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
@Override
public void copyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset, long size) {
throw new UnsupportedOperationException("GLRenderBackend.copyBufferSubData should not be called — LWJGL service handles this");
GL31.glCopyBufferSubData(readTarget, writeTarget, readOffset, writeOffset, size);
}
@Override
@@ -507,11 +517,6 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL11.glPixelStorei(pname, param);
}
@Override
public void pixelStoref(int pname, float param) {
GL11.glPixelStoref(pname, param);
}
@Override
public void sampleCoverage(float value, boolean invert) {
GL13.glSampleCoverage(value, invert);
@@ -617,6 +622,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL11.glGetTexImage(target, level, format, type, pixels);
}
@Override
public void getTexImage(int target, int level, int format, int type, long pixelBufferOffset) {
GL11.glGetTexImage(target, level, format, type, pixelBufferOffset);
}
@Override
public int getFramebufferAttachmentParameteri(int target, int attachment, int pname) {
return GL30.glGetFramebufferAttachmentParameteri(target, attachment, pname);
@@ -817,6 +827,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL20.glUniform4i(location, v0, v1, v2, v3);
}
@Override
public void uniform2(int location, FloatBuffer value) {
GL20.glUniform2(location, value);
}
@Override
public void uniform3(int location, FloatBuffer value) {
GL20.glUniform3(location, value);
@@ -827,6 +842,26 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL20.glUniform4(location, value);
}
@Override
public void uniform1iv(int location, IntBuffer value) {
GL20.glUniform1(location, value);
}
@Override
public void uniform2iv(int location, IntBuffer value) {
GL20.glUniform2(location, value);
}
@Override
public void uniform3iv(int location, IntBuffer value) {
GL20.glUniform3(location, value);
}
@Override
public void uniform4iv(int location, IntBuffer value) {
GL20.glUniform4(location, value);
}
@Override
public void uniformMatrix3(int location, boolean transpose, FloatBuffer value) {
GL20.glUniformMatrix3(location, transpose, value);
@@ -1028,6 +1063,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
return GL30.glMapBufferRange(target, offset, length, access, null);
}
@Override
public void flushMappedBufferRange(int target, long offset, long length) {
GL30.glFlushMappedBufferRange(target, offset, length);
}
@Override
public int genVertexArrays() {
return GL30.glGenVertexArrays();
@@ -1043,6 +1083,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL30.glBindVertexArray(array);
}
@Override
public boolean isVertexArray(int array) {
return GL30.glIsVertexArray(array);
}
@Override
public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer) {
GL20.glVertexAttribPointer(index, size, type, normalized, stride, pointer);
@@ -1214,6 +1259,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
return GL45.glCreateBuffers();
}
@Override
public void namedBufferData(int buffer, long size, int usage) {
GL45.glNamedBufferData(buffer, size, usage);
}
@Override
public void namedBufferData(int buffer, ByteBuffer data, int usage) {
GL45.glNamedBufferData(buffer, data, usage);
@@ -8,6 +8,8 @@ import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.Collections;
import java.util.List;
/**
* Abstract rendering backend.
@@ -25,6 +27,14 @@ public abstract class RenderBackend {
/** Higher priority backends are preferred. */
public int getPriority() { return 0; }
public boolean supportsFileDrop() { return false; }
public void startFileDrop() {}
public void stopFileDrop() {}
public List<String> pollDroppedFiles() { return Collections.emptyList(); }
public abstract int getMinGLSLVersion();
public abstract void flush();
@@ -77,6 +87,8 @@ public abstract class RenderBackend {
public abstract void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride);
public abstract void copyBufferSubData(int readTarget, int writeTarget, long readOffset, long writeOffset, long size);
public abstract void drawElementsInstanced(int mode, int count, int type, long indices, int primcount);
public abstract void drawArraysInstanced(int mode, int first, int count, int primcount);
public void provokingVertex(int provokeMode) {}
public abstract void drawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex);
public abstract void multiDrawElementsBaseVertex(int mode, long pCount, int type, long pIndices, int drawcount, long pBaseVertex);
public abstract void drawBuffer(int mode);
@@ -115,7 +127,6 @@ public abstract class RenderBackend {
public abstract int getTexLevelParameteri(int target, int level, int pname);
public abstract void generateMipmap(int target);
public abstract void pixelStorei(int pname, int param);
public void pixelStoref(int pname, float param) {}
public void sampleCoverage(float value, boolean invert) {}
@@ -140,6 +151,7 @@ public abstract class RenderBackend {
public abstract void readPixels(int x, int y, int width, int height, int format, int type, IntBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, ByteBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, IntBuffer pixels);
public abstract void getTexImage(int target, int level, int format, int type, long pixelBufferOffset);
public abstract int getFramebufferAttachmentParameteri(int target, int attachment, int pname);
public abstract int createShader(int type);
@@ -190,8 +202,13 @@ public abstract class RenderBackend {
public abstract void uniform3i(int location, int v0, int v1, int v2);
public abstract void uniform4f(int location, float v0, float v1, float v2, float v3);
public abstract void uniform4i(int location, int v0, int v1, int v2, int v3);
public abstract void uniform2(int location, FloatBuffer value);
public abstract void uniform3(int location, FloatBuffer value);
public abstract void uniform4(int location, FloatBuffer value);
public abstract void uniform1iv(int location, IntBuffer value);
public abstract void uniform2iv(int location, IntBuffer value);
public abstract void uniform3iv(int location, IntBuffer value);
public abstract void uniform4iv(int location, IntBuffer value);
public abstract void uniformMatrix3(int location, boolean transpose, FloatBuffer value);
public abstract void uniformMatrix4(int location, boolean transpose, FloatBuffer value);
public void uniformMatrix2(int location, boolean transpose, FloatBuffer matrices) {}
@@ -233,10 +250,12 @@ public abstract class RenderBackend {
public abstract int getBufferParameteri(int target, int pname);
public abstract boolean isBuffer(int buffer);
public abstract ByteBuffer mapBufferRange(int target, long offset, long length, int access);
public abstract void flushMappedBufferRange(int target, long offset, long length);
public abstract int genVertexArrays();
public abstract void deleteVertexArrays(int array);
public abstract void bindVertexArray(int array);
public abstract boolean isVertexArray(int array);
public abstract void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer);
public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
vertexAttribPointer(index, size, type, normalized, stride, MemoryUtilities.memAddress0(pointer));
@@ -275,6 +294,7 @@ public abstract class RenderBackend {
public abstract void namedFramebufferDrawBuffers(int framebuffer, IntBuffer bufs);
public abstract void blitNamedFramebuffer(int readFramebuffer, int drawFramebuffer, int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
public abstract int createBuffers();
public abstract void namedBufferData(int buffer, long size, int usage);
public abstract void namedBufferData(int buffer, ByteBuffer data, int usage);
public abstract void namedBufferData(int buffer, FloatBuffer data, int usage);
public abstract void namedBufferSubData(int buffer, long offset, ByteBuffer data);
@@ -104,8 +104,8 @@ public class DSAARB extends DSAUnsupported {
@Override
public int bufferStorage(int target, FloatBuffer data, int usage) {
final int buffer = RENDER_BACKEND.createBuffers();
RENDER_BACKEND.namedBufferData(buffer, data, usage);
final int buffer = GLStateManager.glCreateBuffers();
GLStateManager.glNamedBufferData(buffer, data, usage);
return buffer;
}
@@ -121,9 +121,9 @@ public class DSAUnsupported implements DSAAccess {
@Override
public int bufferStorage(int target, FloatBuffer data, int usage) {
final int buffer = RENDER_BACKEND.genBuffers();
final int buffer = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(target, buffer);
RENDER_BACKEND.bufferData(target, data, usage);
GLStateManager.glBufferData(target, data, usage);
GLStateManager.glBindBuffer(target, 0);
return buffer;
@@ -12,7 +12,7 @@ import org.lwjgl.opengl.GL13;
* Packed-long fragment shader permutation key for FFP emulation.
*
* Layout:
* long[0]: global bits (10) + unit 0 (47 bits at offset 10)
* long[0]: global bits (11) + unit 0 (47 bits at offset 11)
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
* Only max(1, nrEnabledUnits) longs are significant.
*
@@ -63,12 +63,13 @@ public final class FragmentKey {
public static final int FOG_EXP = 2;
public static final int FOG_EXP2 = 3;
private static final int GLOBAL_BITS = 10;
private static final int GLOBAL_BITS = 11;
private static final int BIT_FOG_MODE = 0; // 2 bits
private static final int BIT_ALPHA_TEST = 2; // 1 bit
private static final int BIT_ALPHA_FUNC = 3; // 3 bits
private static final int BIT_SEPARATE_SPECULAR = 6; // 1 bit
private static final int BIT_NR_ENABLED_UNITS = 7; // 3 bits
private static final int BIT_OVERLAY_ENABLED = 10; // 1 bit
private static final int U_ENABLED = 0;
private static final int U_MODE = 1; // 3 bits
@@ -119,6 +120,11 @@ public final class FragmentKey {
global |= ((long) (encodeAlphaFunc(GLStateManager.getAlphaState().getFunction()) & 0x7)) << BIT_ALPHA_FUNC;
}
// Damage overlay
if (GLStateManager.getOverlayA() != 0.0f) {
global |= (1L << BIT_OVERLAY_ENABLED);
}
// Separate specular
if (GLStateManager.getLightingState().isEnabled()
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
@@ -129,7 +135,8 @@ public final class FragmentKey {
int highestEnabled = -1;
long unit0Bits = 0;
for (int i = 0; i < MAX_UNITS; i++) {
final boolean texEnabled = GLStateManager.getTextures().getTextureUnitStates(i).isEnabled();
// Per Mesa - an enabled unit with no complete texture bound counts as disabled.
final boolean texEnabled = GLStateManager.getTextures().getTextureUnitStates(i).isEnabled() && GLStateManager.getTextures().getTextureUnitBindings(i).getBinding() != 0;
if (texEnabled) highestEnabled = i;
final long unitBits = packUnit(i, texEnabled);
@@ -227,6 +234,7 @@ public final class FragmentKey {
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
public int nrEnabledUnits() { return (int) ((packed[0] >> BIT_NR_ENABLED_UNITS) & 0x7); }
public boolean overlayEnabled() { return ((packed[0] >> BIT_OVERLAY_ENABLED) & 1) != 0; }
private long unitBits(int i) {
return (i == 0) ? (packed[0] >>> GLOBAL_BITS) : packed[i];
@@ -252,6 +260,28 @@ public final class FragmentKey {
return false;
}
/** Per-unit enable bits as a 4-bit mask (bit i = unit i enabled). */
public int enabledUnitMask() {
int mask = 0;
final int n = Math.min(nrEnabledUnits(), MAX_UNITS);
for (int i = 0; i < n; i++) {
if (unitEnabled(i)) mask |= (1 << i);
}
return mask;
}
/** Per-unit enable bits from a packed scratch as a 4-bit mask (bit i = unit i enabled). */
public static int unitMaskFromPacked(long[] packed, int len) {
int mask = 0;
if (len < 1) return 0;
if (((packed[0] >>> GLOBAL_BITS) & 1L) != 0) mask |= 1;
final int n = Math.min(len, MAX_UNITS);
for (int i = 1; i < n; i++) {
if ((packed[i] & 1L) != 0) mask |= (1 << i);
}
return mask;
}
public boolean lightmapEnabled() {
return nrEnabledUnits() > 1 && unitEnabled(1);
}
@@ -338,10 +368,10 @@ public final class FragmentKey {
default -> "?";
};
final StringBuilder sb = new StringBuilder();
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b units=%d",
sb.append(String.format("FFPFragmentKey[fog=%s alpha=%b(%s) specSep=%b overlay=%b units=%d",
fogName, alphaTestEnabled(),
alphaTestEnabled() ? String.format("0x%04X", decodeAlphaFunc(alphaTestFunc())) : "-",
separateSpecular(), nrEnabledUnits()));
separateSpecular(), overlayEnabled(), nrEnabledUnits()));
for (int i = 0; i < nrEnabledUnits(); i++) {
if (!unitEnabled(i)) {
sb.append(String.format(" u%d=OFF", i));
@@ -37,11 +37,11 @@ public final class FragmentShaderGenerator {
if (key.separateSpecular()) {
sb.append("in vec3 v_SpecularColor;\n");
}
if (key.textureEnabled()) {
sb.append("in vec4 v_TexCoord0;\n");
}
if (key.lightmapEnabled()) {
sb.append("in vec4 v_TexCoord1;\n");
// Per-unit texcoord inputs. Each enabled unit sources from its own varying
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (key.unitEnabled(i)) {
sb.append("in vec4 v_TexCoord").append(i).append(";\n");
}
}
if (key.fogMode() != FragmentKey.FOG_NONE) {
sb.append("in float v_FogCoord;\n");
@@ -65,6 +65,9 @@ public final class FragmentShaderGenerator {
sb.append("uniform vec4 u_TexEnvColor").append(i).append(";\n");
}
}
if (key.overlayEnabled()) {
sb.append("uniform vec4 u_OverlayColor;\n");
}
if (key.fogMode() != FragmentKey.FOG_NONE) {
sb.append("uniform vec4 u_FogParams;\n");
sb.append("uniform vec4 u_FogColor;\n");
@@ -75,11 +78,7 @@ public final class FragmentShaderGenerator {
private static void emitTextureSampling(StringBuilder sb, FragmentKey key) {
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
// Unit 1 is the lightmap — uses v_TexCoord1.
// Units 2-3 intentionally share unit 0's texture coordinates since
// the vertex shader only provides 2 tex coord varyings (unit 0 and unit 1/lightmap).
final String texCoord = (i == 1) ? "v_TexCoord1.st" : "v_TexCoord0.st";
sb.append(" vec4 tex").append(i).append("Color = texture(u_Sampler").append(i).append(", ").append(texCoord).append(");\n");
sb.append(" vec4 tex").append(i).append("Color = textureProj(u_Sampler").append(i).append(", v_TexCoord").append(i).append(");\n");
}
}
@@ -87,14 +86,21 @@ public final class FragmentShaderGenerator {
if (!key.textureEnabled()) {
sb.append(" // No texture -- vertex color only\n");
sb.append(" vec4 color = v_Color;\n");
emitOverlay(sb, key);
return;
}
// Process units in order; "previous" starts as vertex color
// Process units in order; "previous" starts as vertex color.
boolean firstUnit = true;
boolean overlayInjected = false;
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (!key.unitEnabled(i)) continue;
if (!firstUnit && !overlayInjected) {
emitOverlay(sb, key);
overlayInjected = true;
}
final String texVar = "tex" + i + "Color";
final String envColorVar = "u_TexEnvColor" + i;
final String prevVar = firstUnit ? "v_Color" : "color";
@@ -107,6 +113,16 @@ public final class FragmentShaderGenerator {
}
firstUnit = false;
}
if (!overlayInjected) emitOverlay(sb, key);
}
/**
* Emit the modern-style damage overlay mix.
*/
private static void emitOverlay(StringBuilder sb, FragmentKey key) {
if (!key.overlayEnabled()) return;
sb.append(" color.rgb = mix(color.rgb, u_OverlayColor.rgb, u_OverlayColor.a);\n");
}
private static void emitSimpleUnit(StringBuilder sb, FragmentKey key, int unit, String texVar, String envColorVar, String prevVar, String assign) {
@@ -22,12 +22,10 @@ public final class GeometryShaderGenerator {
if (key.separateSpecular()) {
sb.append("in vec3 v_SpecularColor_gs[];\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
sb.append("in vec4 v_TexCoord0_gs[];\n");
}
if (key.lightmapEnabled()) {
sb.append("in vec4 v_TexCoord1_gs[];\n");
}
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append("in vec4 v_TexCoord0_gs[];\n");
if (key.lightmapEnabled()) sb.append("in vec4 v_TexCoord1_gs[];\n");
if (key.unitTexCoordEnabled(2)) sb.append("in vec4 v_TexCoord2_gs[];\n");
if (key.unitTexCoordEnabled(3)) sb.append("in vec4 v_TexCoord3_gs[];\n");
if (key.fogEnabled()) {
sb.append("in float v_FogCoord_gs[];\n");
}
@@ -37,12 +35,10 @@ public final class GeometryShaderGenerator {
if (key.separateSpecular()) {
sb.append("out vec3 v_SpecularColor;\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
sb.append("out vec4 v_TexCoord0;\n");
}
if (key.lightmapEnabled()) {
sb.append("out vec4 v_TexCoord1;\n");
}
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append("out vec4 v_TexCoord0;\n");
if (key.lightmapEnabled()) sb.append("out vec4 v_TexCoord1;\n");
if (key.unitTexCoordEnabled(2)) sb.append("out vec4 v_TexCoord2;\n");
if (key.unitTexCoordEnabled(3)) sb.append("out vec4 v_TexCoord3;\n");
if (key.fogEnabled()) {
sb.append("out float v_FogCoord;\n");
}
@@ -82,12 +78,10 @@ public final class GeometryShaderGenerator {
if (key.separateSpecular()) {
sb.append(" v_SpecularColor = v_SpecularColor_gs").append(idx).append(";\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
sb.append(" v_TexCoord0 = v_TexCoord0_gs").append(idx).append(";\n");
}
if (key.lightmapEnabled()) {
sb.append(" v_TexCoord1 = v_TexCoord1_gs").append(idx).append(";\n");
}
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) sb.append(" v_TexCoord0 = v_TexCoord0_gs").append(idx).append(";\n");
if (key.lightmapEnabled()) sb.append(" v_TexCoord1 = v_TexCoord1_gs").append(idx).append(";\n");
if (key.unitTexCoordEnabled(2)) sb.append(" v_TexCoord2 = v_TexCoord2_gs").append(idx).append(";\n");
if (key.unitTexCoordEnabled(3)) sb.append(" v_TexCoord3 = v_TexCoord3_gs").append(idx).append(";\n");
if (key.fogEnabled()) {
sb.append(" v_FogCoord = v_FogCoord_gs").append(idx).append(";\n");
}
@@ -26,18 +26,28 @@ public class Program {
@Getter private final VertexKey vertexKey;
@Getter private final FragmentKey fragmentKey;
// Per-program uniform dirty-tracking
static final class UploadState {
int mvGen = -1, mvLinearGen = -1, projGen = -1, texMatGen = -1;
int lightingGen = -1, fragmentGen = -1, colorGen = -1, normalGen = -1, texCoordGen = -1;
int texGenGen = -1, clipPlaneGen = -1;
float lightmapX = Float.NaN, lightmapY = Float.NaN, lineWidth = Float.NaN;
int viewportWidth = -1, viewportHeight = -1;
}
final UploadState uploadState = new UploadState();
// Uniform locations (-1 = not present in this variant)
// Matrices
public int locModelViewMatrix = -1;
public int locProjectionMatrix = -1;
public int locMVPMatrix = -1;
public int locNormalMatrix = -1;
public int locTextureMatrix0 = -1;
public final int[] locTextureMatrix = { -1, -1, -1, -1 };
// Current state defaults
public int locCurrentNormal = -1;
public int locCurrentColor = -1;
public int locCurrentTexCoord = -1;
public final int[] locCurrentTexCoord = { -1, -1, -1, -1 };
public int locCurrentLightmapCoord = -1;
public int locLightmapTextureMatrix = -1;
@@ -92,6 +102,7 @@ public class Program {
public final int[] locSampler = { -1, -1, -1, -1 };
public int locAlphaRef = -1;
public final int[] locTexEnvColor = { -1, -1, -1, -1 };
public int locOverlayColor = -1;
public int locFogParams = -1;
public int locFogColor = -1;
@@ -107,11 +118,13 @@ public class Program {
locProjectionMatrix = loc("u_ProjectionMatrix");
locMVPMatrix = loc("u_MVPMatrix");
locNormalMatrix = loc("u_NormalMatrix");
locTextureMatrix0 = loc("u_TextureMatrix0");
for (int i = 0; i < 4; i++) {
locTextureMatrix[i] = loc("u_TextureMatrix" + i);
locCurrentTexCoord[i] = loc("u_CurrentTexCoord" + i);
}
locCurrentNormal = loc("u_CurrentNormal");
locCurrentColor = loc("u_CurrentColor");
locCurrentTexCoord = loc("u_CurrentTexCoord");
locCurrentLightmapCoord = loc("u_CurrentLightmapCoord");
locLightmapTextureMatrix = loc("u_LightmapTextureMatrix");
@@ -169,6 +182,7 @@ public class Program {
for (int i = 0; i < 4; i++) {
locTexEnvColor[i] = loc("u_TexEnvColor" + i);
}
locOverlayColor = loc("u_OverlayColor");
locFogParams = loc("u_FogParams");
locFogColor = loc("u_FogColor");
}
@@ -1,15 +1,15 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.CompatUniformManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.DeferredBlendHandler;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMHooks;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.stacks.Vec3fStack;
import com.gtnewhorizons.angelica.glsm.stacks.Vec4fStack;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import com.gtnewhorizons.angelica.glsm.streaming.TessellatorStreamingDrawer;
import lombok.Getter;
import org.joml.Vector3f;
import org.joml.Vector4f;
@@ -22,7 +22,7 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
/**
* FFP shader manager
*/
public class ShaderManager {
public final class ShaderManager {
private static final class Holder {
@@ -31,27 +31,33 @@ public class ShaderManager {
private final ShaderCache cache = new ShaderCache();
private final Uniforms uniforms = new Uniforms();
private final Int2IntOpenHashMap vaoVertexFlags = new Int2IntOpenHashMap();
@Getter
private boolean active = false;
private Program currentProgram = null;
private int lastBoundProgramId = -1;
private long currentVertexKeyPacked = Long.MIN_VALUE;
private final long[] currentFKScratch = new long[FragmentKey.MAX_UNITS];
private final long[] currentFKPacked = new long[FragmentKey.MAX_UNITS];
private int currentFKLen = 0;
@Getter private static final Vector3f currentNormal = new Vector3f(0.0f, 0.0f, 1.0f);
@Getter private static final Vector4f currentTexCoord = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f);
private static final Vector4f[] currentTexCoords = {
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
new Vector4f(0.0f, 0.0f, 0.0f, 1.0f),
};
@Getter private static final Vec3fStack normalStack = new Vec3fStack(currentNormal);
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoord);
@Getter private static final Vec4fStack texCoordStack = new Vec4fStack(currentTexCoords[0]);
@Getter private static int normalGeneration;
@Getter private static int texCoordGeneration;
public static Vector4f getCurrentTexCoord() { return currentTexCoords[0]; }
public static Vector4f getCurrentTexCoord(int unit) { return currentTexCoords[unit]; }
@Getter private boolean enabled = false;
private int currentVertexFlags = VertexFlags.TEXTURE_BIT | VertexFlags.COLOR_BIT | VertexFlags.NORMAL_BIT;
private ShaderManager() {
vaoVertexFlags.defaultReturnValue(-1);
if (Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false"))) {
cache.setDumpDir(Paths.get("ffp_shaders"));
}
@@ -62,54 +68,75 @@ public class ShaderManager {
}
public void enable() {
warmUp();
enabled = true;
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
currentVertexFlags = format.getVertexFlags();
vaoVertexFlags.put(GLStateManager.getBoundVAO(), currentVertexFlags);
return false;
});
GLStateManager.LOGGER.info("FFP shader emulation enabled");
}
// Force loading of classes before the SplashThread kicks off
// Ensure it's GL free
private static void warmUp() {
try {
final long[] fkScratch = new long[FragmentKey.MAX_UNITS];
final int fkLen = FragmentKey.packFromState(fkScratch);
final int fragMask = FragmentKey.unitMaskFromPacked(fkScratch, fkLen);
final long vkPacked = VertexKey.packFromState(true, true, true, true, fragMask);
final VertexKey vk = VertexKey.fromPacked(vkPacked);
VertexShaderGenerator.generate(vk);
FragmentShaderGenerator.generate(FragmentKey.fromPacked(fkScratch, fkLen));
GeometryShaderGenerator.generate(vk);
final Class<?>[] touched = { Program.class, ShaderCache.class, TessellatorStreamingDrawer.class, QuadConverter.class };
for (Class<?> c : touched) {
c.getName();
}
} catch (Throwable t) {
GLStateManager.LOGGER.warn("FFP warmup failed; draw-path classes will resolve lazily", t);
}
}
public void disable() {
enabled = false;
}
public void activate() {
active = true;
updateVariant(true, true, true, true);
uploadUniforms();
lastBoundProgramId = GLStateManager.getActiveProgram();
}
public void deactivate() {
active = false;
currentProgram = null;
lastBoundProgramId = -1;
currentVertexKeyPacked = Long.MIN_VALUE;
currentFKLen = 0;
}
public void preDraw(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
GLStateManager.flushDeferredVertexAttribs();
public void preDraw() {
final DeferredBlendHandler bh = GLSMHooks.blendHandler;
if (bh != null) bh.flushDeferredBlend();
if (!active) {
if (enabled) {
final int currentProgramId = GLStateManager.getActiveProgram();
if (currentProgramId != 0) {
CompatUniformManager.onUseProgram(currentProgramId);
return;
}
active = true;
} else {
return;
// Handle FFP & Iris uniforms
final int currentProgramId = GLStateManager.getActiveProgram();
if (currentProgramId != 0) {
if (!CompatUniformManager.refreshCompatUniforms(currentProgramId)) {
return; // Don't emulate FFP on non-iris core shaders
}
}
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
final int vertexFlags = VAOManager.getCurrentVertexFlags();
final boolean hasColor = (vertexFlags & VertexFlags.COLOR_BIT) != 0;
final boolean hasNormal = (vertexFlags & VertexFlags.NORMAL_BIT) != 0;
final boolean hasTexCoord = (vertexFlags & VertexFlags.TEXTURE_BIT) != 0;
final boolean hasLightmap = (vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0;
GLStateManager.flushDeferredVertexAttribs(hasColor, hasNormal, hasTexCoord, hasLightmap);
if (!active) return;
final int fkLen = FragmentKey.packFromState(currentFKScratch);
final int fragMask = FragmentKey.unitMaskFromPacked(currentFKScratch, fkLen);
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragMask);
if (vkPacked != currentVertexKeyPacked || !Arrays.equals(currentFKScratch, 0, fkLen, currentFKPacked, 0, currentFKLen)) {
commitVariant(vkPacked, fkLen);
@@ -118,32 +145,16 @@ public class ShaderManager {
uploadUniforms();
}
public void preDraw(int vertexFlags) {
currentVertexFlags = vertexFlags;
preDraw(
(vertexFlags & VertexFlags.COLOR_BIT) != 0,
(vertexFlags & VertexFlags.NORMAL_BIT) != 0,
(vertexFlags & VertexFlags.TEXTURE_BIT) != 0,
(vertexFlags & VertexFlags.BRIGHTNESS_BIT) != 0
);
}
public void preDraw() {
preDraw(currentVertexFlags);
}
private void updateVariant(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
final long vkPacked = VertexKey.packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap);
final int fkLen = FragmentKey.packFromState(currentFKScratch);
commitVariant(vkPacked, fkLen);
}
private void commitVariant(long vkPacked, int fkLen) {
currentVertexKeyPacked = vkPacked;
System.arraycopy(currentFKScratch, 0, currentFKPacked, 0, fkLen);
currentFKLen = fkLen;
currentProgram = cache.getOrCreate(vkPacked, currentFKPacked, currentFKLen);
RENDER_BACKEND.useProgram(currentProgram.getProgramId());
final int programId = currentProgram.getProgramId();
if (programId != lastBoundProgramId) {
RENDER_BACKEND.useProgram(programId);
lastBoundProgramId = programId;
}
}
private void uploadUniforms() {
@@ -158,29 +169,18 @@ public class ShaderManager {
}
public static void setCurrentTexCoord(float s, float t, float r, float q) {
currentTexCoord.set(s, t, r, q);
currentTexCoords[0].set(s, t, r, q);
texCoordGeneration++;
}
public static void setCurrentTexCoord(int unit, float s, float t, float r, float q) {
currentTexCoords[unit].set(s, t, r, q);
texCoordGeneration++;
}
public static void bumpNormalGeneration() { normalGeneration++; }
public static void bumpTexCoordGeneration() { texCoordGeneration++; }
public void enableClientVertexFlag(int flag) {currentVertexFlags |= flag;}
public void disableClientVertexFlag(int flag) {currentVertexFlags &= ~flag;}
public int getCurrentVertexFlags() {return currentVertexFlags;}
public void setCurrentVertexFlags(int flags) {currentVertexFlags = flags;}
public void onBindVertexArray(int vaoId) {
final int flags = vaoVertexFlags.get(vaoId);
if (flags != -1) {
currentVertexFlags = flags;
}
}
public void onDeleteVertexArray(int vaoId) {
vaoVertexFlags.remove(vaoId);
}
public void destroy() {
cache.destroy();
uniforms.destroy();
@@ -7,6 +7,7 @@ import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.joml.Math;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector4f;
@@ -42,119 +43,102 @@ public class Uniforms {
private final org.joml.Vector3f tempVec3 = new org.joml.Vector3f();
// Pre-computed constants for fog params (Mesa STATE_FOG_PARAMS_OPTIMIZED)
private static final double LN2 = Math.log(2.0);
private static final double SQRT_LN2 = Math.sqrt(LN2);
// Dirty tracking: last-uploaded generation per category + program ID.
// Program change forces full re-upload since uniform locations differ.
private int lastProgramId = -1;
private int lastMvGen = -1;
private int lastProjGen = -1;
private int lastTexMatGen = -1;
private int lastLightingGen = -1;
private int lastFragmentGen = -1;
private int lastColorGen = -1;
private int lastNormalGen = -1;
private int lastTexCoordGen = -1;
private float lastLightmapX = Float.NaN;
private float lastLightmapY = Float.NaN;
private int lastTexGenGen = -1;
private int lastClipPlaneGen = -1;
private float lastLineWidth = Float.NaN;
private int lastViewportWidth = -1;
private int lastViewportHeight = -1;
private static final double LN2 = java.lang.Math.log(2.0);
private static final double SQRT_LN2 = java.lang.Math.sqrt(LN2);
/**
* Upload all relevant uniforms to the given FFP program based on current GLSM state.
* Uses generation counters to skip categories that haven't changed since last upload.
*/
public void upload(Program program) {
final boolean programChanged = program.getProgramId() != lastProgramId;
lastProgramId = program.getProgramId();
final Program.UploadState st = program.uploadState;
final int mvGen = GLStateManager.mvGeneration;
final int mvLinearGen = GLStateManager.mvLinearGeneration;
final int projGen = GLStateManager.projGeneration;
final int texMatGen = GLStateManager.texMatrixGeneration;
final boolean mvChanged = programChanged || mvGen != lastMvGen;
final boolean projChanged = programChanged || projGen != lastProjGen;
final boolean texMatChanged = programChanged || texMatGen != lastTexMatGen;
final boolean mvChanged = mvGen != st.mvGen;
final boolean mvLinearChanged = mvLinearGen != st.mvLinearGen;
final boolean projChanged = projGen != st.projGen;
final boolean texMatChanged = texMatGen != st.texMatGen;
if (mvChanged || projChanged || texMatChanged) {
uploadMatrices(program, mvChanged, projChanged, texMatChanged);
lastMvGen = mvGen;
lastProjGen = projGen;
lastTexMatGen = texMatGen;
uploadMatrices(program, mvChanged, mvLinearChanged, projChanged, texMatChanged);
st.mvGen = mvGen;
st.mvLinearGen = mvLinearGen;
st.projGen = projGen;
st.texMatGen = texMatGen;
}
if (program.getVertexKey().lightingEnabled()) {
final int litGen = GLStateManager.lightingGeneration;
if (programChanged || litGen != lastLightingGen) {
if (litGen != st.lightingGen) {
uploadLighting(program);
lastLightingGen = litGen;
st.lightingGen = litGen;
}
}
// Current color/normal/texcoord — skip if generation unchanged
if (!program.getVertexKey().hasVertexColor()) {
final int colorGen = GLStateManager.colorGeneration;
if (programChanged || colorGen != lastColorGen) {
if (colorGen != st.colorGen) {
uploadCurrentColor(program);
lastColorGen = colorGen;
st.colorGen = colorGen;
}
}
if (!program.getVertexKey().hasVertexNormal() && program.getVertexKey().lightingEnabled()) {
final int normalGen = ShaderManager.getNormalGeneration();
if (programChanged || normalGen != lastNormalGen) {
if (normalGen != st.normalGen) {
uploadCurrentNormal(program);
lastNormalGen = normalGen;
st.normalGen = normalGen;
}
}
if (!program.getVertexKey().hasVertexTexCoord() && program.getVertexKey().textureEnabled()) {
if (program.getVertexKey().anyUnitTexCoordEnabled()) {
final int texGen = ShaderManager.getTexCoordGeneration();
if (programChanged || texGen != lastTexCoordGen) {
uploadCurrentTexCoord(program);
lastTexCoordGen = texGen;
if (texGen != st.texCoordGen) {
uploadCurrentTexCoords(program);
st.texCoordGen = texGen;
}
}
if (program.getVertexKey().lightmapEnabled() && !program.getVertexKey().hasVertexLightmap()) {
if (programChanged || GLSMConfig.lastBrightnessX != lastLightmapX || GLSMConfig.lastBrightnessY != lastLightmapY) {
if (GLSMConfig.lastBrightnessX != st.lightmapX || GLSMConfig.lastBrightnessY != st.lightmapY) {
uploadCurrentLightmapCoord(program);
lastLightmapX = GLSMConfig.lastBrightnessX;
lastLightmapY = GLSMConfig.lastBrightnessY;
st.lightmapX = GLSMConfig.lastBrightnessX;
st.lightmapY = GLSMConfig.lastBrightnessY;
}
}
if (program.getVertexKey().texGenEnabled()) {
final int tgGen = GLStateManager.texGenGeneration;
if (programChanged || tgGen != lastTexGenGen) {
if (tgGen != st.texGenGen) {
uploadTexGen(program);
lastTexGenGen = tgGen;
st.texGenGen = tgGen;
}
}
if (program.getVertexKey().clipPlanesEnabled()) {
final int cpGen = GLStateManager.clipPlaneGeneration;
if (programChanged || cpGen != lastClipPlaneGen) {
if (cpGen != st.clipPlaneGen) {
uploadClipPlanes(program);
lastClipPlaneGen = cpGen;
st.clipPlaneGen = cpGen;
}
}
final int fragGen = GLStateManager.fragmentGeneration;
if (programChanged || fragGen != lastFragmentGen) {
if (fragGen != st.fragmentGen) {
uploadFragmentUniforms(program);
lastFragmentGen = fragGen;
st.fragmentGen = fragGen;
}
// Wide line emulation uniforms
if (program.locLineWidth != -1 && program.locViewportSize != -1) {
uploadWideLineUniforms(program, programChanged);
uploadWideLineUniforms(program);
}
}
private void uploadMatrices(Program program, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
private void uploadMatrices(Program program, boolean mvChanged, boolean mvLinearChanged, boolean projChanged, boolean texMatChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
@@ -164,7 +148,9 @@ public class Uniforms {
mv.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locModelViewMatrix, false, mat4Buf);
}
}
if (mvLinearChanged) {
// Normal matrix = inverse transpose of upper-left 3x3 of ModelView
if (program.locNormalMatrix != -1 || program.locNormalScale != -1) {
mv.normal(normalMatrix);
@@ -197,16 +183,15 @@ public class Uniforms {
RENDER_BACKEND.uniformMatrix4(program.locMVPMatrix, false, mat4Buf);
}
// Texture matrices
// Texture matrices (per-unit).
if (texMatChanged) {
// Texture matrix unit 0
if (program.locTextureMatrix0 != -1) {
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
texMat.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locTextureMatrix0, false, mat4Buf);
for (int i = 0; i < 4; i++) {
if (program.locTextureMatrix[i] != -1) {
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(i);
texMat.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locTextureMatrix[i], false, mat4Buf);
}
}
// Texture matrix unit 1 (lightmap)
if (program.locLightmapTextureMatrix != -1) {
final Matrix4f lmTexMat = GLStateManager.getTextures().getTextureUnitMatrix(1);
lmTexMat.get(mat4Buf);
@@ -314,19 +299,22 @@ public class Uniforms {
if (program.locCurrentColor == -1) return;
// Upload the current color from GLSM
final var color = GLStateManager.getColor();
tempVec4.set(
Math.clamp(0f, 1f, color.getRed()),
Math.clamp(0f, 1f, color.getGreen()),
Math.clamp(0f, 1f, color.getBlue()),
Math.clamp(0f, 1f, color.getAlpha()));
vec4Buf.clear();
vec4Buf.put(color.getRed());
vec4Buf.put(color.getGreen());
vec4Buf.put(color.getBlue());
vec4Buf.put(color.getAlpha());
vec4Buf.flip();
tempVec4.get(vec4Buf);
RENDER_BACKEND.uniform4(program.locCurrentColor, vec4Buf);
}
private void uploadCurrentTexCoord(Program program) {
if (program.locCurrentTexCoord == -1) return;
final var tc = ShaderManager.getCurrentTexCoord();
uploadVec4(program.locCurrentTexCoord, tc);
private void uploadCurrentTexCoords(Program program) {
for (int i = 0; i < 4; i++) {
if (program.locCurrentTexCoord[i] != -1) {
uploadVec4(program.locCurrentTexCoord[i], ShaderManager.getCurrentTexCoord(i));
}
}
}
private void uploadCurrentLightmapCoord(Program program) {
@@ -340,10 +328,7 @@ public class Uniforms {
// Upload current normal from FFPShaderManager's tracked normal
final var normal = ShaderManager.getCurrentNormal();
vec3Buf.clear();
vec3Buf.put(normal.x);
vec3Buf.put(normal.y);
vec3Buf.put(normal.z);
vec3Buf.flip();
normal.get(vec3Buf);
RENDER_BACKEND.uniform3(program.locCurrentNormal, vec3Buf);
}
@@ -379,10 +364,7 @@ public class Uniforms {
private void uploadPlane(int loc, float[] plane) {
if (loc == -1) return;
vec4Buf.clear();
vec4Buf.put(plane[0]);
vec4Buf.put(plane[1]);
vec4Buf.put(plane[2]);
vec4Buf.put(plane[3]);
vec4Buf.put(plane, 0, 4);
vec4Buf.flip();
RENDER_BACKEND.uniform4(loc, vec4Buf);
}
@@ -420,6 +402,16 @@ public class Uniforms {
}
}
if (fk.overlayEnabled() && program.locOverlayColor != -1) {
vec4Buf.clear();
vec4Buf.put(GLStateManager.getOverlayR());
vec4Buf.put(GLStateManager.getOverlayG());
vec4Buf.put(GLStateManager.getOverlayB());
vec4Buf.put(GLStateManager.getOverlayA());
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locOverlayColor, vec4Buf);
}
// Fog
if (fk.fogMode() != FragmentKey.FOG_NONE) {
uploadFog(program);
@@ -458,29 +450,26 @@ public class Uniforms {
}
}
private void uploadWideLineUniforms(Program program, boolean programChanged) {
private void uploadWideLineUniforms(Program program) {
final Program.UploadState st = program.uploadState;
final float lineWidth = GLStateManager.getLineState().getWidth();
if (programChanged || lineWidth != lastLineWidth) {
if (lineWidth != st.lineWidth) {
RENDER_BACKEND.uniform1f(program.locLineWidth, lineWidth);
lastLineWidth = lineWidth;
st.lineWidth = lineWidth;
}
final int vw = GLStateManager.getViewportState().width;
final int vh = GLStateManager.getViewportState().height;
if (programChanged || vw != lastViewportWidth || vh != lastViewportHeight) {
if (vw != st.viewportWidth || vh != st.viewportHeight) {
RENDER_BACKEND.uniform2f(program.locViewportSize, vw, vh);
lastViewportWidth = vw;
lastViewportHeight = vh;
st.viewportWidth = vw;
st.viewportHeight = vh;
}
}
private void uploadVec4(int loc, Vector4f v) {
if (loc == -1) return;
vec4Buf.clear();
vec4Buf.put(v.x);
vec4Buf.put(v.y);
vec4Buf.put(v.z);
vec4Buf.put(v.w);
vec4Buf.flip();
v.get(vec4Buf);
RENDER_BACKEND.uniform4(loc, vec4Buf);
}
@@ -0,0 +1,321 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import javax.annotation.Nullable;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public final class VAOManager {
public static final int MAX_ATTRIBS = 16;
public static final Int2ObjectOpenHashMap<VAOData> vaoMap = new Int2ObjectOpenHashMap<>();
private static VAOData current = null;
private static int currentVertexFlags = 0;
private static Attrib[] currentAttribs = null;
public static int boundEBO = 0;
private static int clientSideEnabledCount = 0;
public static void init(int defaultVAO) {
final VAOData data = new VAOData();
current = data;
currentAttribs = data.attribs;
vaoMap.put(defaultVAO, data);
}
public static void onBindEBO(int ebo) {
boundEBO = ebo;
}
public static void onBindVertexArrayPre(int vaoId) {
// Save old VAO data
if (current != null) {
current.vertexFlags = currentVertexFlags;
current.ebo = boundEBO;
current.attribs = currentAttribs;
current.clientSideEnabledCount = clientSideEnabledCount;
}
VAOData data = vaoMap.get(vaoId);
if (data == null) {
data = new VAOData();
vaoMap.put(vaoId, data);
}
current = data;
currentVertexFlags = data.vertexFlags;
currentAttribs = data.attribs;
boundEBO = data.ebo;
clientSideEnabledCount = data.clientSideEnabledCount;
}
public static void onDeleteVertexArray(int vaoId) {
vaoMap.remove(vaoId);
}
public static void enableClientVertexFlag(int flag) { currentVertexFlags |= flag; }
public static void disableClientVertexFlag(int flag) { currentVertexFlags &= ~flag; }
public static int getCurrentVertexFlags() { return currentVertexFlags; }
public static void setCurrentVertexFlags(int flags) { currentVertexFlags = flags; }
public static final class VAOData {
public Attrib[] attribs;
public int vertexFlags;
public int ebo;
public int clientSideEnabledCount;
public VAOData() {
attribs = new Attrib[MAX_ATTRIBS];
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = offset;
a.vboId = vboId;
a.clientPointer = null;
a.genericPointer = true;
if (wasClient && a.enabled) {
clientSideEnabledCount--;
}
}
public static void setAttribute(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
final boolean wasClient = a.clientPointer != null;
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = 0;
a.vboId = 0;
a.clientPointer = pointer;
a.genericPointer = true;
if (!wasClient && a.enabled) {
clientSideEnabledCount++;
}
}
public static boolean isGenericPointerEnabled(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return false;
final Attrib a = currentAttribs[index];
return a != null && a.genericPointer && a.enabled;
}
public static void markConventional(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = currentAttribs[index];
if (a != null) a.genericPointer = false;
}
public static void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled != enabled) {
clientSideEnabledCount += enabled ? 1 : -1;
}
a.enabled = enabled;
}
public static void enableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && !a.enabled) {
clientSideEnabledCount++;
}
a.enabled = true;
}
public static void disableAttribute(int index) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = getAttrib(index);
if (a.clientPointer != null && a.enabled) {
clientSideEnabledCount--;
}
a.enabled = false;
}
// Lazily allocate each attrib. Most VAOs don't even have 4 attribs, let alone 16.
private static Attrib getAttrib(int index) {
Attrib a = currentAttribs[index];
if (a == null) {
a = new Attrib();
currentAttribs[index] = a;
}
return a;
}
public static boolean isClientSideAttrib(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null && attrib.isClientSide();
}
public static int getAttribEffectiveStride(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.effectiveStride() : 0;
}
public static long getAttribOffset(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.offset : 0;
}
public static int getAttribVBO(int index) {
final Attrib attrib = currentAttribs[index];
return attrib != null ? attrib.vboId : 0;
}
public static @Nullable Attrib get(int index) {
return currentAttribs[index];
}
/**
* Returns true if any currently enabled vertex attribute was registered without a VBO
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
* offsets into a non-existent buffer, causing a native crash at draw time.
*/
public static boolean hasAnyClientSideEnabledAttrib() {
return clientSideEnabledCount > 0;
}
/**
* If any enabled vertex attribute uses a client-side pointer (no VBO), upload all such
* attribs into a shared stream VBO so the draw succeeds under core profile.
*/
private static int clientArraysVBO = 0;
private static int clientArraysVBOCapacity = 0;
private static final int[] clientArraysVBOOffsets = new int[VAOManager.MAX_ATTRIBS];
public static void uploadClientArraysToVBO() {
int totalBytes = 0;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
clientArraysVBOOffsets[i] = -1;
final VAOManager.Attrib a = VAOManager.get(i);
if (a == null || !a.enabled || a.clientPointer == null) continue;
clientArraysVBOOffsets[i] = totalBytes;
totalBytes += a.clientPointer.remaining();
}
if (totalBytes == 0) return;
if (clientArraysVBO == 0) {
clientArraysVBO = RENDER_BACKEND.genBuffers();
}
final int savedVBO = GLStateManager.getBoundVBO();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, clientArraysVBO);
if (totalBytes > clientArraysVBOCapacity) {
int newCap = Math.max(4096, clientArraysVBOCapacity);
while (newCap < totalBytes) newCap *= 2;
clientArraysVBOCapacity = newCap;
}
RENDER_BACKEND.bufferData(GL15.GL_ARRAY_BUFFER, clientArraysVBOCapacity, GL15.GL_STREAM_DRAW);
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
if (clientArraysVBOOffsets[i] < 0) continue;
final VAOManager.Attrib a = VAOManager.get(i);
RENDER_BACKEND.bufferSubData(GL15.GL_ARRAY_BUFFER, clientArraysVBOOffsets[i], a.clientPointer.duplicate());
RENDER_BACKEND.vertexAttribPointer(i, a.size, a.type, a.normalized, a.stride, (long) clientArraysVBOOffsets[i]);
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, savedVBO);
}
public static final class Attrib {
public boolean enabled;
public int size;
public int type;
public boolean normalized;
public int stride;
public long offset;
public int vboId;
public ByteBuffer clientPointer;
public boolean genericPointer;
public boolean isClientSide() {
return enabled && clientPointer != null;
}
public void reset() {
enabled = false;
size = 0;
type = 0;
normalized = false;
stride = 0;
offset = 0;
vboId = 0;
clientPointer = null;
genericPointer = false;
}
public int effectiveStride() {
return (stride != 0) ? stride : size * typeSizeBytes();
}
public int typeSizeBytes() {
return glTypeSizeBytes(type);
}
public static int glTypeSizeBytes(int glType) {
return switch (glType) {
case GL11.GL_FLOAT, GL11.GL_INT, GL11.GL_UNSIGNED_INT -> 4;
case GL11.GL_DOUBLE -> 8;
case GL11.GL_SHORT, GL11.GL_UNSIGNED_SHORT -> 2;
case GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE -> 1;
default -> 4;
};
}
public float readComponent(ByteBuffer buf, int base, int component) {
return readComponent(type, normalized, buf, base, component);
}
public static float readComponent(int type, boolean normalized, ByteBuffer buf, int base, int component) {
return switch (type) {
case GL11.GL_FLOAT -> buf.getFloat(base + component * 4);
case GL11.GL_DOUBLE -> (float) buf.getDouble(base + component * 8);
case GL11.GL_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? v / (float) Integer.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? (v & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL : (float) (v & 0xFFFFFFFFL);
}
case GL11.GL_SHORT -> {
final short v = buf.getShort(base + component * 2);
yield normalized ? v / (float) Short.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_SHORT -> {
final int v = buf.getShort(base + component * 2) & 0xFFFF;
yield normalized ? v / (float) 0xFFFF : (float) v;
}
case GL11.GL_BYTE -> {
final byte v = buf.get(base + component);
yield normalized ? v / 127.0f : (float) v;
}
case GL11.GL_UNSIGNED_BYTE -> {
final int v = buf.get(base + component) & 0xFF;
yield normalized ? v / 255.0f : (float) v;
}
default -> 0.0f;
};
}
}
}
@@ -1,8 +1,10 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizon.gtnhlib.client.renderer.MatrixHelper;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import org.joml.Matrix4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
@@ -10,9 +12,14 @@ import org.lwjgl.opengl.GL12;
/**
* Compact key capturing the vertex shader permutation axes for FFP emulation.
* Packed into a single long for fast hash/compare.
*
* Per-unit fields are 4-bit base-indexed: bit (BASE + i) for unit i in [0..3].
* Unit 1 is the lightmap; texcoord enable for unit 1 also gates the lightmap-specific code paths.
*/
public final class VertexKey {
public static final int MAX_UNITS = 4;
private final long packed;
private static final int BIT_LIGHTING = 0;
@@ -23,26 +30,26 @@ public final class VertexKey {
private static final int BIT_COLOR_MATERIAL = 5;
private static final int BIT_SEPARATE_SPECULAR = 6;
private static final int BIT_FOG = 7;
private static final int BIT_FOG_DIST_MODE = 8;
private static final int BIT_FOG_DIST_MODE = 8; // 2 bits: 8-9
private static final int BIT_NORMALIZE = 10;
private static final int BIT_RESCALE_NORMAL = 11;
private static final int BIT_TEXTURE = 12;
private static final int BIT_LIGHTMAP = 13;
private static final int BIT_TEX_MATRIX = 14;
private static final int BIT_HAS_VERTEX_COLOR = 15;
private static final int BIT_HAS_VERTEX_NORMAL = 16;
private static final int BIT_HAS_VERTEX_TEX = 17;
private static final int BIT_HAS_VERTEX_LIGHTMAP = 18;
private static final int BIT_COLOR_MAT_MODE = 19;
// bits 19-21: colorMaterialMode (3 bits)
// TexGen modes: 3 bits each (0=NONE, 1=OBJ_LINEAR, 2=EYE_LINEAR)
private static final int BIT_TEXGEN_S = 22;
private static final int BIT_TEXGEN_T = 25;
private static final int BIT_TEXGEN_R = 28;
private static final int BIT_TEXGEN_Q = 31;
// bits 31-33: texGenModeQ (3 bits)
private static final int BIT_CLIP_PLANES = 34;
private static final int BIT_WIDE_LINE = 35;
// Per-unit texcoord enable: bits 12-15 (unit 0..3). Unit 1 doubles as the lightmap flag.
private static final int BIT_UNIT_TEX_BASE = 12;
private static final int BIT_HAS_VERTEX_COLOR = 16;
private static final int BIT_HAS_VERTEX_NORMAL = 17;
private static final int BIT_HAS_VERTEX_TEX = 18;
private static final int BIT_HAS_VERTEX_LIGHTMAP = 19;
private static final int BIT_COLOR_MAT_MODE = 20; // 3 bits: 20-22
// TexGen modes (unit 0 only, for now): 3 bits each (0=NONE, 1=OBJ_LINEAR, 2=EYE_LINEAR).
private static final int BIT_TEXGEN_S = 23; // 3 bits: 23-25
private static final int BIT_TEXGEN_T = 26; // 3 bits: 26-28
private static final int BIT_TEXGEN_R = 29; // 3 bits: 29-31
private static final int BIT_TEXGEN_Q = 32; // 3 bits: 32-34
private static final int BIT_CLIP_PLANES = 35;
private static final int BIT_WIDE_LINE = 36;
// Per-unit texmat enable: bits 37-40 (unit 0..3). Unit 1 is the lightmap matrix.
private static final int BIT_UNIT_TEXMAT_BASE = 37;
private static final int BIT_UNIT23_UV_FROM_UNIT0 = 41;
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
@@ -71,38 +78,30 @@ public final class VertexKey {
public int fogDistanceMode() { return (int)((packed >> BIT_FOG_DIST_MODE) & 0x3); }
public boolean normalizeEnabled() { return bit(BIT_NORMALIZE); }
public boolean rescaleNormalsEnabled() { return bit(BIT_RESCALE_NORMAL); }
public boolean textureEnabled() { return bit(BIT_TEXTURE); }
public boolean lightmapEnabled() { return bit(BIT_LIGHTMAP); }
public boolean textureMatrixEnabled() { return bit(BIT_TEX_MATRIX); }
public boolean unitTexCoordEnabled(int unit) { return bit(BIT_UNIT_TEX_BASE + unit); }
public boolean unitTexMatEnabled(int unit) { return bit(BIT_UNIT_TEXMAT_BASE + unit); }
public boolean anyUnitTexCoordEnabled() { return ((packed >> BIT_UNIT_TEX_BASE) & 0xF) != 0; }
public boolean lightmapEnabled() { return unitTexCoordEnabled(1); }
public boolean hasVertexColor() { return bit(BIT_HAS_VERTEX_COLOR); }
public boolean hasVertexNormal() { return bit(BIT_HAS_VERTEX_NORMAL); }
public boolean hasVertexTexCoord() { return bit(BIT_HAS_VERTEX_TEX); }
public boolean hasVertexLightmap() { return bit(BIT_HAS_VERTEX_LIGHTMAP); }
public boolean unit23UvFromUnit0() { return bit(BIT_UNIT23_UV_FROM_UNIT0); }
/** Color material mode (CM_AMBIENT, CM_DIFFUSE, CM_SPECULAR, CM_EMISSION, CM_AMBIENT_AND_DIFFUSE). Only meaningful when colorMaterialEnabled(). */
public int colorMaterialMode() { return (int)((packed >> BIT_COLOR_MAT_MODE) & 0x7); }
/** TexGen mode for S coordinate (TG_NONE, TG_OBJ_LINEAR, TG_EYE_LINEAR). */
/** TexGen mode for S coordinate (TG_NONE, TG_OBJ_LINEAR, TG_EYE_LINEAR). Unit 0 only; see TODO in packFromState. */
public int texGenModeS() { return (int)((packed >> BIT_TEXGEN_S) & 0x7); }
/** TexGen mode for T coordinate. */
public int texGenModeT() { return (int)((packed >> BIT_TEXGEN_T) & 0x7); }
/** TexGen mode for R coordinate. */
public int texGenModeR() { return (int)((packed >> BIT_TEXGEN_R) & 0x7); }
/** TexGen mode for Q coordinate. */
public int texGenModeQ() { return (int)((packed >> BIT_TEXGEN_Q) & 0x7); }
/** Whether any texgen coordinate is enabled. */
public boolean texGenEnabled() { return texGenModeS() != TG_NONE || texGenModeT() != TG_NONE || texGenModeR() != TG_NONE || texGenModeQ() != TG_NONE; }
/** Whether any clip plane is enabled. */
public boolean clipPlanesEnabled() { return bit(BIT_CLIP_PLANES); }
/** Whether wide line GS emulation is active for this variant. */
public boolean wideLineEmulation() { return bit(BIT_WIDE_LINE); }
/** Whether vertex color replaces material ambient in this variant. */
public boolean cmReplacesAmbient() { final int m = colorMaterialMode(); return m == CM_AMBIENT || m == CM_AMBIENT_AND_DIFFUSE; }
/** Whether vertex color replaces material diffuse in this variant. */
public boolean cmReplacesDiffuse() { final int m = colorMaterialMode(); return m == CM_DIFFUSE || m == CM_AMBIENT_AND_DIFFUSE; }
/** Whether vertex color replaces material specular in this variant. */
public boolean cmReplacesSpecular() { return colorMaterialMode() == CM_SPECULAR; }
/** Whether vertex color replaces material emission in this variant. */
public boolean cmReplacesEmission() { return colorMaterialMode() == CM_EMISSION; }
private boolean bit(int pos) { return ((packed >> pos) & 1) != 0; }
@@ -126,7 +125,7 @@ public final class VertexKey {
};
}
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
public static long packFromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
long bits = 0;
final boolean lighting = GLStateManager.getLightingState().isEnabled();
@@ -171,23 +170,18 @@ public final class VertexKey {
bits |= (1L << BIT_RESCALE_NORMAL);
}
// Texture unit 0 (base texture)
if (GLStateManager.getTextures().getTextureUnitStates(0).isEnabled()) {
bits |= (1L << BIT_TEXTURE);
}
// Texture unit 1 (lightmap)
if (GLStateManager.getTextures().getTextureUnitStates(1).isEnabled()) {
bits |= (1L << BIT_LIGHTMAP);
final TextureUnitArray texUnit = GLStateManager.getTextures();
for (int i = 0; i < MAX_UNITS; i++) {
if ((fragUnitMask & (1 << i)) != 0) {
bits |= (1L << (BIT_UNIT_TEX_BASE + i));
}
if (!MatrixHelper.isIdentity(texUnit.getTextureUnitMatrix(i))) {
bits |= (1L << (BIT_UNIT_TEXMAT_BASE + i));
}
}
// Check if texture matrix unit 0 is non-identity
final Matrix4f texMat = GLStateManager.getTextures().getTextureUnitMatrix(0);
if (!DisplayListManager.isIdentity(texMat)) {
bits |= (1L << BIT_TEX_MATRIX);
}
// TexGen (unit 0 only) — encode per-coordinate mode if enabled
final var texUnit = GLStateManager.getTextures();
// TexGen (unit 0 only) - per-coordinate mode if enabled.
// TODO: per-unit texgen for units 2/3. Deferred - no MC code path uses glTexGen on those.
final TexGenState tg = texUnit.getTexGenState(0);
boolean anyTexGen = false;
if (texUnit.getTexGenSStates(0).isEnabled() && tg.getMode(GL11.GL_S) != 0) {
@@ -210,9 +204,9 @@ public final class VertexKey {
bits |= ((long) m & 0x7) << BIT_TEXGEN_Q;
if (m != TG_NONE) anyTexGen = true;
}
// Force texture matrix on when texgen is active (end portal pattern always uses it)
// Force unit-0 texture matrix on when texgen is active (end portal pattern always uses it).
if (anyTexGen) {
bits |= (1L << BIT_TEX_MATRIX);
bits |= (1L << BIT_UNIT_TEXMAT_BASE);
}
// Vertex format flags
@@ -221,12 +215,14 @@ public final class VertexKey {
if (hasTexCoord) bits |= (1L << BIT_HAS_VERTEX_TEX);
if (hasLightmap) bits |= (1L << BIT_HAS_VERTEX_LIGHTMAP);
// Clip planes
if (GLStateManager.consumeUnit23TexCoordSetDuringDraw() && hasTexCoord) {
bits |= (1L << BIT_UNIT23_UV_FROM_UNIT0);
}
if (GLStateManager.anyClipPlaneEnabled()) {
bits |= (1L << BIT_CLIP_PLANES);
}
// Wide line GS emulation
if (GLStateManager.wideLineEmulationActive) {
bits |= (1L << BIT_WIDE_LINE);
}
@@ -234,8 +230,8 @@ public final class VertexKey {
return bits;
}
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap) {
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap));
public static VertexKey fromState(boolean hasColor, boolean hasNormal, boolean hasTexCoord, boolean hasLightmap, int fragUnitMask) {
return new VertexKey(packFromState(hasColor, hasNormal, hasTexCoord, hasLightmap, fragUnitMask));
}
static VertexKey fromPacked(long packed) {
@@ -256,9 +252,11 @@ public final class VertexKey {
@Override
public String toString() {
return String.format("FFPVertexKey[0x%09X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%b lm=%b col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b]",
return String.format("FFPVertexKey[0x%011X: lit=%b l0=%b l1=%b cm=%b fog=%b tex=%d%d%d%d texmat=%d%d%d%d col=%b nrm=%b vtex=%b vlm=%b tg=%d/%d/%d/%d clip=%b wline=%b]",
packed, lightingEnabled(), light0Enabled(), light1Enabled(),
colorMaterialEnabled(), fogEnabled(), textureEnabled(), lightmapEnabled(),
colorMaterialEnabled(), fogEnabled(),
unitTexCoordEnabled(0)?1:0, unitTexCoordEnabled(1)?1:0, unitTexCoordEnabled(2)?1:0, unitTexCoordEnabled(3)?1:0,
unitTexMatEnabled(0)?1:0, unitTexMatEnabled(1)?1:0, unitTexMatEnabled(2)?1:0, unitTexMatEnabled(3)?1:0,
hasVertexColor(), hasVertexNormal(), hasVertexTexCoord(), hasVertexLightmap(),
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation());
}
@@ -42,6 +42,8 @@ public final class VertexShaderGenerator {
source = source.replace("v_SpecularColor", "v_SpecularColor_gs")
.replace("v_TexCoord0", "v_TexCoord0_gs")
.replace("v_TexCoord1", "v_TexCoord1_gs")
.replace("v_TexCoord2", "v_TexCoord2_gs")
.replace("v_TexCoord3", "v_TexCoord3_gs")
.replace("v_FogCoord", "v_FogCoord_gs")
.replace("v_Color", "v_Color_gs");
}
@@ -76,11 +78,14 @@ public final class VertexShaderGenerator {
sb.append("uniform mat3 u_NormalMatrix;\n");
}
if (key.textureMatrixEnabled()) {
sb.append("uniform mat4 u_TextureMatrix0;\n");
for (int i = 0; i < VertexKey.MAX_UNITS; i++) {
if (i == 1) continue;
if (key.unitTexMatEnabled(i)) {
sb.append("uniform mat4 u_TextureMatrix").append(i).append(";\n");
}
}
// TexGen plane uniforms
// TexGen plane uniforms (unit 0 only - TODO: per-unit)
if (key.texGenModeS() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneS;\n");
if (key.texGenModeS() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneS;\n");
if (key.texGenModeT() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneT;\n");
@@ -98,9 +103,11 @@ public final class VertexShaderGenerator {
sb.append("uniform vec4 u_CurrentColor;\n");
}
if (!key.hasVertexTexCoord() && key.textureEnabled()) {
sb.append("uniform vec4 u_CurrentTexCoord;\n");
if (key.unitTexCoordEnabled(0) && !key.hasVertexTexCoord()) {
sb.append("uniform vec4 u_CurrentTexCoord0;\n");
}
if (key.unitTexCoordEnabled(2) && !key.unit23UvFromUnit0()) sb.append("uniform vec4 u_CurrentTexCoord2;\n");
if (key.unitTexCoordEnabled(3) && !key.unit23UvFromUnit0()) sb.append("uniform vec4 u_CurrentTexCoord3;\n");
if (key.lightmapEnabled() && !key.hasVertexLightmap()) {
sb.append("uniform vec2 u_CurrentLightmapCoord;\n");
@@ -178,12 +185,15 @@ public final class VertexShaderGenerator {
if (key.separateSpecular()) {
sb.append("out vec3 v_SpecularColor;\n");
}
if (key.textureEnabled() || key.hasVertexTexCoord() || key.texGenEnabled()) {
// Per-unit varying outputs. Each enabled texture unit (and texgen, which targets unit 0) gets its own v_TexCoord<i>.
if (key.unitTexCoordEnabled(0) || key.texGenEnabled()) {
sb.append("out vec4 v_TexCoord0;\n");
}
if (key.lightmapEnabled()) {
sb.append("out vec4 v_TexCoord1;\n");
}
if (key.unitTexCoordEnabled(2)) sb.append("out vec4 v_TexCoord2;\n");
if (key.unitTexCoordEnabled(3)) sb.append("out vec4 v_TexCoord3;\n");
if (key.fogEnabled()) {
sb.append("out float v_FogCoord;\n");
}
@@ -331,22 +341,18 @@ public final class VertexShaderGenerator {
private static void emitTexCoordPassthrough(StringBuilder sb, VertexKey key) {
if (key.texGenEnabled()) {
emitTexGenCoordGeneration(sb, key);
} else if (key.textureEnabled() || key.hasVertexTexCoord()) {
sb.append(" // Texture coordinates\n");
if (key.hasVertexTexCoord()) {
if (key.textureMatrixEnabled()) {
sb.append(" v_TexCoord0 = u_TextureMatrix0 * vec4(a_TexCoord0, 0.0, 1.0);\n");
} else {
sb.append(" v_TexCoord0 = vec4(a_TexCoord0, 0.0, 1.0);\n");
}
} else if (key.unitTexCoordEnabled(0)) {
sb.append(" // Texture coordinates - unit 0\n");
final String src = key.hasVertexTexCoord()
? "vec4(a_TexCoord0, 0.0, 1.0)"
: "u_CurrentTexCoord0";
if (key.unitTexMatEnabled(0)) {
sb.append(" v_TexCoord0 = u_TextureMatrix0 * ").append(src).append(";\n");
} else {
if (key.textureMatrixEnabled()) {
sb.append(" v_TexCoord0 = u_TextureMatrix0 * u_CurrentTexCoord;\n");
} else {
sb.append(" v_TexCoord0 = u_CurrentTexCoord;\n");
}
sb.append(" v_TexCoord0 = ").append(src).append(";\n");
}
}
if (key.lightmapEnabled()) {
if (key.hasVertexLightmap()) {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
@@ -354,6 +360,18 @@ public final class VertexShaderGenerator {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(u_CurrentLightmapCoord, 0.0, 1.0);\n");
}
}
for (int i = 2; i < VertexKey.MAX_UNITS; i++) {
if (!key.unitTexCoordEnabled(i)) continue;
final String src = key.unit23UvFromUnit0()
? "vec4(a_TexCoord0, 0.0, 1.0)"
: "u_CurrentTexCoord" + i;
if (!key.unit23UvFromUnit0() && key.unitTexMatEnabled(i)) {
sb.append(" v_TexCoord").append(i).append(" = u_TextureMatrix").append(i).append(" * ").append(src).append(";\n");
} else {
sb.append(" v_TexCoord").append(i).append(" = ").append(src).append(";\n");
}
}
}
/**
@@ -367,7 +385,7 @@ public final class VertexShaderGenerator {
emitTexGenComponent(sb, key.texGenModeS(), "s", "S", key);
emitTexGenComponent(sb, key.texGenModeT(), "t", "T", key);
emitTexGenComponent(sb, key.texGenModeR(), "r", "R", key);
emitTexGenComponent(sb, key.texGenModeR(), "p", "R", key);
emitTexGenComponent(sb, key.texGenModeQ(), "q", "Q", key);
// Always apply texture matrix when texgen is active (forced on in VertexKey)
@@ -8,6 +8,7 @@ public final class GLSMHooks {
public static DeferredBlendHandler blendHandler;
public static DeferredAlphaHandler alphaHandler;
public static DeferredDepthColorHandler depthColorHandler;
public static GlintColorHandler glintColorHandler;
// EventBus instances
public static final EventBus<TextureBindEvent> TEXTURE_BIND = EventBus.create(TextureBindEvent.class);
@@ -18,6 +19,7 @@ public final class GLSMHooks {
public static final EventBus<FogStateChangeEvent> FOG_STATE_CHANGE = EventBus.create(FogStateChangeEvent.class);
public static final EventBus<LightmapCoordsEvent> LIGHTMAP_COORDS = EventBus.create(LightmapCoordsEvent.class);
public static final EventBus<AlphaStateChangeEvent> ALPHA_STATE_CHANGE = EventBus.create(AlphaStateChangeEvent.class);
public static final EventBus<ShaderColorChangeEvent> SHADER_COLOR_CHANGE = EventBus.create(ShaderColorChangeEvent.class);
// Reusable event instances
public static final TextureBindEvent textureBindEvent = new TextureBindEvent();
@@ -28,6 +30,7 @@ public final class GLSMHooks {
public static final FogStateChangeEvent fogStateChangeEvent = new FogStateChangeEvent();
public static final LightmapCoordsEvent lightmapCoordsEvent = new LightmapCoordsEvent();
public static final AlphaStateChangeEvent alphaStateChangeEvent = new AlphaStateChangeEvent();
public static final ShaderColorChangeEvent shaderColorChangeEvent = new ShaderColorChangeEvent();
private GLSMHooks() {}
}
@@ -8,24 +8,24 @@ import java.util.function.Consumer;
public final class GLSMInitConfig {
private final boolean lwjglDebug;
private final StreamingUploader.UploadStrategy streamingUploadStrategy;
private final boolean framebufferSupported;
private final boolean fboEnabled;
private final Consumer<DirectTessellator> directDrawer;
private final Runnable streamingDrawerDestroy;
private final int displayWidth;
private final int displayHeight;
private final Runnable postInitCallback;
private final boolean enableDSA;
private final boolean noErrorChecks;
private GLSMInitConfig(Builder builder) {
this.lwjglDebug = builder.lwjglDebug;
this.streamingUploadStrategy = builder.streamingUploadStrategy;
this.framebufferSupported = builder.framebufferSupported;
this.fboEnabled = builder.fboEnabled;
this.noErrorChecks = builder.noErrorChecks;
this.directDrawer = builder.directDrawer;
this.streamingDrawerDestroy = builder.streamingDrawerDestroy;
this.displayWidth = builder.displayWidth;
this.displayHeight = builder.displayHeight;
this.postInitCallback = builder.postInitCallback;
this.enableDSA = builder.enableDSA;
}
public static Builder builder() {
@@ -34,24 +34,24 @@ public final class GLSMInitConfig {
public boolean isLwjglDebug() { return lwjglDebug; }
public StreamingUploader.UploadStrategy getStreamingUploadStrategy() { return streamingUploadStrategy; }
public boolean isFramebufferSupported() { return framebufferSupported; }
public boolean isFboEnabled() { return fboEnabled; }
public Consumer<DirectTessellator> getDirectDrawer() { return directDrawer; }
public Runnable getStreamingDrawerDestroy() { return streamingDrawerDestroy; }
public int getDisplayWidth() { return displayWidth; }
public int getDisplayHeight() { return displayHeight; }
public Runnable getPostInitCallback() { return postInitCallback; }
public boolean isDSAEnabled() { return enableDSA; }
public boolean noErrorChecks() { return noErrorChecks; }
public static final class Builder {
private boolean lwjglDebug = false;
private StreamingUploader.UploadStrategy streamingUploadStrategy = StreamingUploader.UploadStrategy.BUFFER_DATA;
private boolean framebufferSupported = false;
private boolean fboEnabled = false;
private Consumer<DirectTessellator> directDrawer = null;
private Runnable streamingDrawerDestroy = null;
private int displayWidth = 0;
private int displayHeight = 0;
private Runnable postInitCallback = null;
private boolean enableDSA = false;
private boolean noErrorChecks;
private Builder() {}
@@ -65,13 +65,8 @@ public final class GLSMInitConfig {
return this;
}
public Builder framebufferSupported(boolean framebufferSupported) {
this.framebufferSupported = framebufferSupported;
return this;
}
public Builder fboEnabled(boolean fboEnabled) {
this.fboEnabled = fboEnabled;
public Builder noErrorChecks(boolean noErrorChecks) {
this.noErrorChecks = noErrorChecks;
return this;
}
@@ -96,6 +91,11 @@ public final class GLSMInitConfig {
return this;
}
public Builder enableDSA(boolean enableDSA) {
this.enableDSA = enableDSA;
return this;
}
public GLSMInitConfig build() {
return new GLSMInitConfig(this);
}
@@ -0,0 +1,5 @@
package com.gtnewhorizons.angelica.glsm.hooks;
public interface GlintColorHandler {
void onColorChanged(float red, float green, float blue, float alpha);
}
@@ -5,4 +5,5 @@ import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class ProgramChangeEvent extends MutableEvent {
public int previousProgram;
public int newProgram;
public boolean postBind;
}
@@ -0,0 +1,10 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class ShaderColorChangeEvent extends MutableEvent {
public float red;
public float green;
public float blue;
public float alpha;
}
@@ -35,11 +35,30 @@ public final class EcosystemNarrowRules {
"com.fiskmods.lightsabers.asm.transformers"
)),
new NarrowRule("FiskHeroes", "com.fiskmods.heroes.asm", List.of(
"com.fiskmods.heroes.asm.SHLoadingPlugin",
"com.fiskmods.heroes.asm.transformers"
)),
new NarrowRule("Ears", "com.unascribed.ears", List.of(
"com.unascribed.ears.asm",
"com.unascribed.ears.common.agent"
)),
new NarrowRule("FoamFix", "pl.asie.foamfix", List.of(
"pl.asie.foamfix.bugfixmod",
"pl.asie.foamfix.coremod",
"pl.asie.foamfix.forkage",
"pl.asie.foamfix.ghostbuster",
"pl.asie.foamfix.FoamFixMod",
"pl.asie.foamfix.HackyMessageFactoryWrapper",
"pl.asie.foamfix.LaunchWrapperRuntimeFix",
"pl.asie.foamfix.ProxyClient",
"pl.asie.foamfix.ProxyCommon",
"pl.asie.foamfix.repack.com.unascribed.ears.asm",
"pl.asie.foamfix.repack.com.unascribed.ears.common.agent"
)),
new NarrowRule("Alfheim", "alfheim.common.core.asm.hook", List.of(
"alfheim.common.core.asm.hook.AlfheimFieldHookHandler",
"alfheim.common.core.asm.hook.AlfheimHPHooks",
@@ -16,17 +16,13 @@ public final class AccumulatedDraw {
public final VertexFormat format;
public final List<ByteBuffer> drawBuffers;
public final int drawMode;
public final int commandIndex; // Position in command list for state tracking
public final int stateGeneration; // Position in command list for state tracking
public RestoreData restoreData;
public AccumulatedDraw(DirectTessellator tessellator, int commandIndex, int stateGeneration, boolean copyLast) {
public AccumulatedDraw(DirectTessellator tessellator, boolean copyLast) {
this.format = tessellator.getVertexFormat();
this.drawMode = tessellator.drawMode;
this.drawBuffers = new ArrayList<>();
this.drawBuffers.add(tessellator.allocateBufferCopy());
this.commandIndex = commandIndex;
this.stateGeneration = stateGeneration;
if (copyLast) {
this.restoreData = new RestoreData(tessellator);
@@ -36,7 +32,6 @@ public final class AccumulatedDraw {
public void mergeDraw(DirectTessellator tessellator, boolean copyLast) {
this.drawBuffers.add(tessellator.allocateBufferCopy());
if (copyLast) {
this.restoreData = new RestoreData(tessellator);
}
@@ -2,7 +2,7 @@ package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import it.unimi.dsi.fastutil.ints.Int2LongOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL15;
@@ -15,11 +15,11 @@ public final class AttribSnapshot {
public record AttribDesc(int location, int size, int type, boolean normalized, int stride, long offset, int sourceVboId, ByteBuffer readBuffer, long readBufferBaseOffset) {
public int effectiveStride() {
return stride != 0 ? stride : size * VertexAttribState.Attrib.glTypeSizeBytes(type);
return stride != 0 ? stride : size * VAOManager.Attrib.glTypeSizeBytes(type);
}
public int typeSizeBytes() {
return VertexAttribState.Attrib.glTypeSizeBytes(type);
return VAOManager.Attrib.glTypeSizeBytes(type);
}
}
@@ -56,7 +56,7 @@ public final class AttribSnapshot {
* allocating any hashmaps. A second distinct VBO promotes to the map-backed fallback.
*/
public static AttribSnapshot snapshot(int firstVertex, int vertexCount) {
final AttribDesc[] out = new AttribDesc[VertexAttribState.MAX_ATTRIBS];
final AttribDesc[] out = new AttribDesc[VAOManager.MAX_ATTRIBS];
final List<ByteBuffer> allocated = new ArrayList<>();
final int prevVBO = GLStateManager.getBoundVBO();
@@ -67,11 +67,11 @@ public final class AttribSnapshot {
Int2ObjectOpenHashMap<long[]> vboRanges = null;
final long lastVtx = (long) firstVertex + vertexCount - 1;
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!a.enabled || a.vboId == 0) continue;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
if (a == null || !a.enabled || a.vboId == 0) continue;
final int stride = a.effectiveStride();
final long typeBytes = (long) a.size * VertexAttribState.Attrib.glTypeSizeBytes(a.type);
final long typeBytes = (long) a.size * VAOManager.Attrib.glTypeSizeBytes(a.type);
final long start = a.offset + (long) firstVertex * stride;
final long end = a.offset + lastVtx * stride + typeBytes;
@@ -103,9 +103,9 @@ public final class AttribSnapshot {
Int2LongOpenHashMap vboReadOffsets = null;
try {
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!a.enabled) continue;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final VAOManager.Attrib a = VAOManager.get(i);
if (a == null || !a.enabled) continue;
if (a.vboId != 0) {
ByteBuffer buf;
@@ -1,767 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.joml.Matrix4f;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
public final class CommandBuffer {
private static final int DEFAULT_CAPACITY = 4096; // Guess at typical display list size
private ByteBuffer buffer;
private long basePointer;
private long writePointer;
private final List<Object> complexObjects = new ArrayList<>();
public CommandBuffer() {
this(DEFAULT_CAPACITY);
}
public CommandBuffer(int initialCapacity) {
this.buffer = memAlloc(initialCapacity);
this.basePointer = memAddress(buffer);
this.writePointer = basePointer;
}
// === Low-level write operations ===
private void writeInt(int value) {
memPutInt(writePointer, value);
writePointer += 4;
}
private void writeLong(long value) {
memPutLong(writePointer, value);
writePointer += 8;
}
private void writeFloat(float value) {
memPutFloat(writePointer, value);
writePointer += 4;
}
private void writeDouble(double value) {
memPutDouble(writePointer, value);
writePointer += 8;
}
private void ensureCapacity(int needed) {
final int size = size();
final int remaining = buffer.capacity() - size;
if (remaining < needed) {
final int newCapacity = Math.max(buffer.capacity() * 2, buffer.capacity() + needed);
buffer = memRealloc(buffer, newCapacity);
basePointer = memAddress(buffer);
writePointer = basePointer + size;
}
}
// === Single int commands ===
public void writeEnable(int cap) {
ensureCapacity(8);
writeInt(GLCommand.ENABLE);
writeInt(cap);
}
public void writeDisable(int cap) {
ensureCapacity(8);
writeInt(GLCommand.DISABLE);
writeInt(cap);
}
public void writeClear(int mask) {
ensureCapacity(8);
writeInt(GLCommand.CLEAR);
writeInt(mask);
}
public void writeClearStencil(int s) {
ensureCapacity(8);
writeInt(GLCommand.CLEAR_STENCIL);
writeInt(s);
}
public void writeCullFace(int mode) {
ensureCapacity(8);
writeInt(GLCommand.CULL_FACE);
writeInt(mode);
}
public void writeDepthFunc(int func) {
ensureCapacity(8);
writeInt(GLCommand.DEPTH_FUNC);
writeInt(func);
}
public void writeShadeModel(int mode) {
ensureCapacity(8);
writeInt(GLCommand.SHADE_MODEL);
writeInt(mode);
}
public void writeLogicOp(int opcode) {
ensureCapacity(8);
writeInt(GLCommand.LOGIC_OP);
writeInt(opcode);
}
public void writeMatrixMode(int mode) {
ensureCapacity(8);
writeInt(GLCommand.MATRIX_MODE);
writeInt(mode);
}
public void writeActiveTexture(int texture) {
ensureCapacity(8);
writeInt(GLCommand.ACTIVE_TEXTURE);
writeInt(texture);
}
public void writeUseProgram(int program) {
ensureCapacity(8);
writeInt(GLCommand.USE_PROGRAM);
writeInt(program);
}
public void writePushAttrib(int mask) {
ensureCapacity(8);
writeInt(GLCommand.PUSH_ATTRIB);
writeInt(mask);
}
public void writePopAttrib() {
ensureCapacity(8);
writeInt(GLCommand.POP_ATTRIB);
writeInt(0); // unused padding
}
public void writeLoadIdentity() {
ensureCapacity(4);
writeInt(GLCommand.LOAD_IDENTITY);
}
public void writePushMatrix() {
ensureCapacity(4);
writeInt(GLCommand.PUSH_MATRIX);
}
public void writePopMatrix() {
ensureCapacity(4);
writeInt(GLCommand.POP_MATRIX);
}
public void writeStencilMask(int mask) {
ensureCapacity(8);
writeInt(GLCommand.STENCIL_MASK);
writeInt(mask);
}
public void writeDepthMask(boolean flag) {
ensureCapacity(8);
writeInt(GLCommand.DEPTH_MASK);
writeInt(flag ? 1 : 0);
}
public void writeFrontFace(int mode) {
ensureCapacity(8);
writeInt(GLCommand.FRONT_FACE);
writeInt(mode);
}
// === Two int commands ===
public void writeBindTexture(int target, int texture) {
ensureCapacity(12);
writeInt(GLCommand.BIND_TEXTURE);
writeInt(target);
writeInt(texture);
}
public void writePolygonMode(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_MODE);
writeInt(face);
writeInt(mode);
}
public void writeColorMaterial(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.COLOR_MATERIAL);
writeInt(face);
writeInt(mode);
}
public void writeLineStipple(int factor, int pattern) {
ensureCapacity(12);
writeInt(GLCommand.LINE_STIPPLE);
writeInt(factor);
writeInt(pattern);
}
public void writeStencilMaskSeparate(int face, int mask) {
ensureCapacity(12);
writeInt(GLCommand.STENCIL_MASK_SEPARATE);
writeInt(face);
writeInt(mask);
}
public void writeFogi(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.FOGI);
writeInt(pname);
writeInt(param);
}
public void writeHint(int target, int mode) {
ensureCapacity(12);
writeInt(GLCommand.HINT);
writeInt(target);
writeInt(mode);
}
// === Three int commands ===
public void writeStencilFunc(int func, int ref, int mask) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_FUNC);
writeInt(func);
writeInt(ref);
writeInt(mask);
}
public void writeStencilOp(int fail, int zfail, int zpass) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_OP);
writeInt(fail);
writeInt(zfail);
writeInt(zpass);
}
public void writeTexParameteri(int target, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.TEX_PARAMETERI);
writeInt(target);
writeInt(pname);
writeInt(param);
}
// === Four int commands ===
public void writeViewport(int x, int y, int width, int height) {
ensureCapacity(20);
writeInt(GLCommand.VIEWPORT);
writeInt(x);
writeInt(y);
writeInt(width);
writeInt(height);
}
public void writeScissor(int x, int y, int width, int height) {
ensureCapacity(20);
writeInt(GLCommand.SCISSOR);
writeInt(x);
writeInt(y);
writeInt(width);
writeInt(height);
}
public void writeBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
ensureCapacity(20);
writeInt(GLCommand.BLEND_FUNC);
writeInt(srcRgb);
writeInt(dstRgb);
writeInt(srcAlpha);
writeInt(dstAlpha);
}
public void writeColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
ensureCapacity(20);
writeInt(GLCommand.COLOR_MASK);
writeInt(red ? 1 : 0);
writeInt(green ? 1 : 0);
writeInt(blue ? 1 : 0);
writeInt(alpha ? 1 : 0);
}
public void writeStencilFuncSeparate(int face, int func, int ref, int mask) {
ensureCapacity(20);
writeInt(GLCommand.STENCIL_FUNC_SEPARATE);
writeInt(face);
writeInt(func);
writeInt(ref);
writeInt(mask);
}
public void writeStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
ensureCapacity(20);
writeInt(GLCommand.STENCIL_OP_SEPARATE);
writeInt(face);
writeInt(sfail);
writeInt(dpfail);
writeInt(dppass);
}
// === Float commands ===
public void writePointSize(float size) {
ensureCapacity(8);
writeInt(GLCommand.POINT_SIZE);
writeFloat(size);
}
public void writeLineWidth(float width) {
ensureCapacity(8);
writeInt(GLCommand.LINE_WIDTH);
writeFloat(width);
}
public void writePolygonOffset(float factor, float units) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_OFFSET);
writeFloat(factor);
writeFloat(units);
}
public void writeColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void writeClearColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.CLEAR_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void writeClearDepth(double depth) {
ensureCapacity(12);
writeInt(GLCommand.CLEAR_DEPTH);
writeDouble(depth);
}
public void writeBlendColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.BLEND_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
// === Mixed int+float commands ===
public void writeAlphaFunc(int func, float ref) {
ensureCapacity(12);
writeInt(GLCommand.ALPHA_FUNC);
writeInt(func);
writeFloat(ref);
}
public void writeFogf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.FOGF);
writeInt(pname);
writeFloat(param);
}
public void writeLightf(int light, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTF);
writeInt(light);
writeInt(pname);
writeFloat(param);
}
public void writeLightModelf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELF);
writeInt(pname);
writeFloat(param);
}
public void writeLighti(int light, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTI);
writeInt(light);
writeInt(pname);
writeInt(param);
}
public void writeLightModeli(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELI);
writeInt(pname);
writeInt(param);
}
public void writeMaterialf(int face, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.MATERIALF);
writeInt(face);
writeInt(pname);
writeFloat(param);
}
public void writeTexParameterf(int target, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.TEX_PARAMETERF);
writeInt(target);
writeInt(pname);
writeFloat(param);
}
// === Double commands ===
public void writeTranslate(double x, double y, double z) {
ensureCapacity(28);
writeInt(GLCommand.TRANSLATE);
writeDouble(x);
writeDouble(y);
writeDouble(z);
}
public void writeRotate(double angle, double x, double y, double z) {
ensureCapacity(36);
writeInt(GLCommand.ROTATE);
writeDouble(angle);
writeDouble(x);
writeDouble(y);
writeDouble(z);
}
public void writeScale(double x, double y, double z) {
ensureCapacity(28);
writeInt(GLCommand.SCALE);
writeDouble(x);
writeDouble(y);
writeDouble(z);
}
public void writeOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
ensureCapacity(52);
writeInt(GLCommand.ORTHO);
writeDouble(left);
writeDouble(right);
writeDouble(bottom);
writeDouble(top);
writeDouble(zNear);
writeDouble(zFar);
}
public void writeFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
ensureCapacity(52);
writeInt(GLCommand.FRUSTUM);
writeDouble(left);
writeDouble(right);
writeDouble(bottom);
writeDouble(top);
writeDouble(zNear);
writeDouble(zFar);
}
// === Matrix commands ===
public void writeMultMatrix(Matrix4f matrix) {
ensureCapacity(68);
writeInt(GLCommand.MULT_MATRIX);
writeMatrix4f(matrix);
}
public void writeLoadMatrix(Matrix4f matrix) {
ensureCapacity(68);
writeInt(GLCommand.LOAD_MATRIX);
writeMatrix4f(matrix);
}
/**
* Write a Matrix4f to the buffer in column-major order (OpenGL convention).
* Uses accessor methods instead of getToAddress() to avoid JOML MemUtil issues.
*/
private void writeMatrix4f(Matrix4f m) {
// Column 0
writeFloat(m.m00()); writeFloat(m.m01()); writeFloat(m.m02()); writeFloat(m.m03());
// Column 1
writeFloat(m.m10()); writeFloat(m.m11()); writeFloat(m.m12()); writeFloat(m.m13());
// Column 2
writeFloat(m.m20()); writeFloat(m.m21()); writeFloat(m.m22()); writeFloat(m.m23());
// Column 3
writeFloat(m.m30()); writeFloat(m.m31()); writeFloat(m.m32()); writeFloat(m.m33());
}
// === Buffer commands (inline 4 floats) ===
public void writeFog(int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(12 + count * 4);
writeInt(GLCommand.FOG);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeLight(int light, int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(16 + count * 4);
writeInt(GLCommand.LIGHT);
writeInt(light);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeLightModel(int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(12 + count * 4);
writeInt(GLCommand.LIGHT_MODEL);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeMaterial(int face, int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(16 + count * 4);
writeInt(GLCommand.MATERIAL);
writeInt(face);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeClipPlane(int plane, double a, double b, double c, double d) {
ensureCapacity(40);
writeInt(GLCommand.CLIP_PLANE);
writeInt(plane);
writeDouble(a);
writeDouble(b);
writeDouble(c);
writeDouble(d);
}
// === Draw commands ===
public void writeDrawRange(int vboIndex) {
ensureCapacity(8);
writeInt(GLCommand.DRAW_RANGE);
writeInt(vboIndex);
}
/**
* Write a DRAW_RANGE_RESTORE command that draws a VBO range and restores GL current state after.
* Used for immediate mode VBOs to restore GL_CURRENT_COLOR, GL_CURRENT_NORMAL, GL_CURRENT_TEXTURE_COORDS.
*
* @param vboIndex Index into ownedVbos array
*/
public void writeDrawRangeRestore(
int vboIndex, AccumulatedDraw.RestoreData restoreData
) {
ensureCapacity(28);
writeInt(GLCommand.DRAW_RANGE_RESTORE);
writeInt(vboIndex);
writeInt(restoreData.flags);
// Last color (4 bytes)
writeInt(restoreData.lastColor);
// Last normal (4 bytes)
writeInt(restoreData.lastNormal);
// Last texcoord (2 floats = 8 bytes)
writeFloat(restoreData.lastTexCoordU);
writeFloat(restoreData.lastTexCoordV);
}
public void writeCallList(int listId) {
ensureCapacity(8);
writeInt(GLCommand.CALL_LIST);
writeInt(listId);
}
public void writeBindVBO(int vbo) {
ensureCapacity(8);
writeInt(GLCommand.BIND_VBO);
writeInt(vbo);
}
public void writeBindVAO(int vao) {
ensureCapacity(8);
writeInt(GLCommand.BIND_VAO);
writeInt(vao);
}
public void writeDrawBuffer(int mode) {
ensureCapacity(8);
writeInt(GLCommand.DRAW_BUFFER);
writeInt(mode);
}
private static final int MAX_DRAW_BUFFERS = 8;
// cmd (4) + count (4) + buffers (4 * MAX_DRAW_BUFFERS)
private static final int DRAW_BUFFERS_SIZE = 4 + 4 + 4 * MAX_DRAW_BUFFERS;
public void writeDrawBuffers(int count, int buffer) {
ensureCapacity(DRAW_BUFFERS_SIZE);
writeInt(GLCommand.DRAW_BUFFERS);
writeInt(count);
writeInt(buffer);
for (int i = 1; i < MAX_DRAW_BUFFERS; i++) {
writeInt(0);
}
}
public void writeDrawBuffers(int count, IntBuffer bufs) {
ensureCapacity(DRAW_BUFFERS_SIZE);
writeInt(GLCommand.DRAW_BUFFERS);
writeInt(count);
final int pos = bufs.position();
for (int i = 0; i < count && i < MAX_DRAW_BUFFERS; i++) {
writeInt(bufs.get(pos + i));
}
for (int i = count; i < MAX_DRAW_BUFFERS; i++) {
writeInt(0);
}
}
// === Complex object reference ===
public void writeComplexRef(DisplayListCommand cmd) {
ensureCapacity(8);
final int index = complexObjects.size();
complexObjects.add(cmd);
writeInt(GLCommand.COMPLEX_REF);
writeInt(index);
}
// === Reading methods for optimization pass ===
private long readPointer = 0;
/**
* Reset read position to the beginning of the buffer.
*/
public void resetRead() {
readPointer = basePointer;
}
/**
* Check if there's more data to read.
*/
public boolean hasRemaining() {
return readPointer < writePointer;
}
/**
* Read an int from the current position.
*/
public int readInt() {
final int value = memGetInt(readPointer);
readPointer += 4;
return value;
}
public long readLong() {
final long value = memGetLong(readPointer);
readPointer += 8;
return value;
}
/**
* Read a float from the current position.
*/
public float readFloat() {
final float value = memGetFloat(readPointer);
readPointer += 4;
return value;
}
/**
* Read a double from the current position.
*/
public double readDouble() {
final double value = memGetDouble(readPointer);
readPointer += 8;
return value;
}
/**
* Read a Matrix4f from the current position (16 floats in column-major order).
*/
public Matrix4f readMatrix4f(Matrix4f dest) {
dest.m00(readFloat()); dest.m01(readFloat()); dest.m02(readFloat()); dest.m03(readFloat());
dest.m10(readFloat()); dest.m11(readFloat()); dest.m12(readFloat()); dest.m13(readFloat());
dest.m20(readFloat()); dest.m21(readFloat()); dest.m22(readFloat()); dest.m23(readFloat());
dest.m30(readFloat()); dest.m31(readFloat()); dest.m32(readFloat()); dest.m33(readFloat());
return dest;
}
// === Lifecycle ===
public int size() {
return (int) (writePointer - basePointer);
}
public long address() {
return basePointer;
}
public ByteBuffer toBuffer() {
buffer.limit(size());
buffer.position(0);
return buffer;
}
public Object[] getComplexObjects() {
return complexObjects.toArray();
}
public Object getComplexObject(int index) {
return complexObjects.get(index);
}
public boolean isEmpty() {
return size() == 0 && complexObjects.isEmpty();
}
/**
* Clear the buffer for reuse. Does NOT free memory.
*/
public void clear() {
writePointer = basePointer;
complexObjects.clear();
}
/**
* Free the underlying buffer. Must be called when done.
*/
public void free() {
if (buffer != null) {
memFree(buffer);
buffer = null;
basePointer = 0;
writePointer = 0;
}
}
}
@@ -1,12 +1,10 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
@@ -14,7 +12,10 @@ import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetDouble;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
/**
* Executes commands from a CommandBuffer
@@ -39,7 +40,13 @@ public final class CommandBufferExecutor {
* @param complexObjects Array of complex objects (TexImage2DCmd, etc.)
* @param ownedVbos Array of VBOs owned by the display list (indexed by DrawRange commands)
*/
public static void execute(ByteBuffer buffer, Object[] complexObjects, DisplayListVBO ownedVbos) {
public static void execute(
ByteBuffer buffer,
DisplayListCommand[] complexObjects,
DisplayListVBO ownedVbos,
int list,
CompiledDisplayList compiledDisplayList
) {
long ptr = memAddress(buffer);
final long end = ptr + buffer.limit();
@@ -343,50 +350,6 @@ public final class CommandBufferExecutor {
GLStateManager.glTexParameterf(target, pname, param);
}
// === Double commands ===
case GLCommand.TRANSLATE -> {
final double x = memGetDouble(ptr);
final double y = memGetDouble(ptr + 8);
final double z = memGetDouble(ptr + 16);
ptr += 24;
GLStateManager.glTranslated(x, y, z);
}
case GLCommand.ROTATE -> {
final double angle = memGetDouble(ptr);
final double x = memGetDouble(ptr + 8);
final double y = memGetDouble(ptr + 16);
final double z = memGetDouble(ptr + 24);
ptr += 32;
GLStateManager.glRotated(angle, x, y, z);
}
case GLCommand.SCALE -> {
final double x = memGetDouble(ptr);
final double y = memGetDouble(ptr + 8);
final double z = memGetDouble(ptr + 16);
ptr += 24;
GLStateManager.glScaled(x, y, z);
}
case GLCommand.ORTHO -> {
final double left = memGetDouble(ptr);
final double right = memGetDouble(ptr + 8);
final double bottom = memGetDouble(ptr + 16);
final double top = memGetDouble(ptr + 24);
final double zNear = memGetDouble(ptr + 32);
final double zFar = memGetDouble(ptr + 40);
ptr += 48;
GLStateManager.glOrtho(left, right, bottom, top, zNear, zFar);
}
case GLCommand.FRUSTUM -> {
final double left = memGetDouble(ptr);
final double right = memGetDouble(ptr + 8);
final double bottom = memGetDouble(ptr + 16);
final double top = memGetDouble(ptr + 24);
final double zNear = memGetDouble(ptr + 32);
final double zFar = memGetDouble(ptr + 40);
ptr += 48;
GLStateManager.glFrustum(left, right, bottom, top, zNear, zFar);
}
// === Matrix commands ===
case GLCommand.MULT_MATRIX -> {
// Mode-agnostic: just multiply current matrix
@@ -398,6 +361,20 @@ public final class CommandBufferExecutor {
MATRIX_BUFFER.flip();
GLStateManager.glMultMatrix(MATRIX_BUFFER);
}
case GLCommand.TRANSLATE -> {
final float x = memGetFloat(ptr);
final float y = memGetFloat(ptr + 4);
final float z = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glTranslatef(x, y, z);
}
case GLCommand.SCALE -> {
final float x = memGetFloat(ptr);
final float y = memGetFloat(ptr + 4);
final float z = memGetFloat(ptr + 8);
ptr += 12;
GLStateManager.glScalef(x, y, z);
}
case GLCommand.LOAD_MATRIX -> {
MATRIX_BUFFER.clear();
for (int i = 0; i < 16; i++) {
@@ -543,10 +520,10 @@ public final class CommandBufferExecutor {
case GLCommand.COMPLEX_REF -> {
final int index = memGetInt(ptr);
ptr += 4;
((DisplayListCommand) complexObjects[index]).execute();
complexObjects[index].execute();
}
default -> throw new IllegalStateException("Unknown command opcode: " + cmd);
default -> throw new IllegalStateException("Unknown command opcode: " + cmd + " in display list with ID " + list + "\n" + DisplayListManager.getCompiledDisplayListString(list, compiledDisplayList, null));
}
}
}
@@ -1,166 +0,0 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import org.joml.Matrix4f;
import java.nio.FloatBuffer;
public final class CommandBufferProcessor {
private static final Matrix4f tempMatrix = new Matrix4f();
private CommandBufferProcessor() {}
/**
* Copy a complete command (opcode already read) from raw to final buffer.
*/
public static void copyCommand(CommandBuffer raw, CommandBuffer out) {
final int opcode = raw.readInt();
copyCommandData(opcode, raw, out);
}
/**
* Copy command data based on opcode (opcode already consumed from raw).
*/
public static void copyCommandData(int opcode, CommandBuffer raw, CommandBuffer out) {
switch (opcode) {
// Single int commands
case GLCommand.ENABLE -> out.writeEnable(raw.readInt());
case GLCommand.DISABLE -> out.writeDisable(raw.readInt());
case GLCommand.CLEAR -> out.writeClear(raw.readInt());
case GLCommand.CLEAR_STENCIL -> out.writeClearStencil(raw.readInt());
case GLCommand.CULL_FACE -> out.writeCullFace(raw.readInt());
case GLCommand.DEPTH_FUNC -> out.writeDepthFunc(raw.readInt());
case GLCommand.SHADE_MODEL -> out.writeShadeModel(raw.readInt());
case GLCommand.LOGIC_OP -> out.writeLogicOp(raw.readInt());
case GLCommand.MATRIX_MODE -> out.writeMatrixMode(raw.readInt());
case GLCommand.ACTIVE_TEXTURE -> out.writeActiveTexture(raw.readInt());
case GLCommand.USE_PROGRAM -> out.writeUseProgram(raw.readInt());
case GLCommand.PUSH_ATTRIB -> out.writePushAttrib(raw.readInt());
case GLCommand.POP_ATTRIB -> { raw.readInt(); out.writePopAttrib(); }
case GLCommand.LOAD_IDENTITY -> out.writeLoadIdentity();
case GLCommand.PUSH_MATRIX -> out.writePushMatrix();
case GLCommand.POP_MATRIX -> out.writePopMatrix();
case GLCommand.STENCIL_MASK -> out.writeStencilMask(raw.readInt());
case GLCommand.DEPTH_MASK -> out.writeDepthMask(raw.readInt() != 0);
case GLCommand.FRONT_FACE -> out.writeFrontFace(raw.readInt());
// Two int commands
case GLCommand.BIND_TEXTURE -> out.writeBindTexture(raw.readInt(), raw.readInt());
case GLCommand.POLYGON_MODE -> out.writePolygonMode(raw.readInt(), raw.readInt());
case GLCommand.COLOR_MATERIAL -> out.writeColorMaterial(raw.readInt(), raw.readInt());
case GLCommand.LINE_STIPPLE -> out.writeLineStipple(raw.readInt(), raw.readInt());
case GLCommand.STENCIL_MASK_SEPARATE -> out.writeStencilMaskSeparate(raw.readInt(), raw.readInt());
case GLCommand.FOGI -> out.writeFogi(raw.readInt(), raw.readInt());
case GLCommand.HINT -> out.writeHint(raw.readInt(), raw.readInt());
// Three int commands
case GLCommand.STENCIL_FUNC -> out.writeStencilFunc(raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.STENCIL_OP -> out.writeStencilOp(raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.TEX_PARAMETERI -> out.writeTexParameteri(raw.readInt(), raw.readInt(), raw.readInt());
// Four int commands
case GLCommand.VIEWPORT -> out.writeViewport(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.SCISSOR -> out.writeScissor(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.BLEND_FUNC -> out.writeBlendFunc(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.COLOR_MASK -> out.writeColorMask(raw.readInt() != 0, raw.readInt() != 0, raw.readInt() != 0, raw.readInt() != 0);
case GLCommand.STENCIL_FUNC_SEPARATE -> out.writeStencilFuncSeparate(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.STENCIL_OP_SEPARATE -> out.writeStencilOpSeparate(raw.readInt(), raw.readInt(), raw.readInt(), raw.readInt());
// Float commands
case GLCommand.POINT_SIZE -> out.writePointSize(raw.readFloat());
case GLCommand.LINE_WIDTH -> out.writeLineWidth(raw.readFloat());
case GLCommand.POLYGON_OFFSET -> out.writePolygonOffset(raw.readFloat(), raw.readFloat());
case GLCommand.COLOR -> out.writeColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
case GLCommand.CLEAR_COLOR -> out.writeClearColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
// Single double commands
case GLCommand.CLEAR_DEPTH -> out.writeClearDepth(raw.readDouble());
// Four float commands
case GLCommand.BLEND_COLOR -> out.writeBlendColor(raw.readFloat(), raw.readFloat(), raw.readFloat(), raw.readFloat());
// Mixed int+float commands
case GLCommand.ALPHA_FUNC -> out.writeAlphaFunc(raw.readInt(), raw.readFloat());
case GLCommand.FOGF -> out.writeFogf(raw.readInt(), raw.readFloat());
case GLCommand.LIGHTF -> out.writeLightf(raw.readInt(), raw.readInt(), raw.readFloat());
case GLCommand.LIGHT_MODELF -> out.writeLightModelf(raw.readInt(), raw.readFloat());
case GLCommand.LIGHTI -> out.writeLighti(raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.LIGHT_MODELI -> out.writeLightModeli(raw.readInt(), raw.readInt());
case GLCommand.MATERIALF -> out.writeMaterialf(raw.readInt(), raw.readInt(), raw.readFloat());
case GLCommand.TEX_PARAMETERF -> out.writeTexParameterf(raw.readInt(), raw.readInt(), raw.readFloat());
// Double commands
case GLCommand.TRANSLATE -> out.writeTranslate(raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.ROTATE -> out.writeRotate(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.SCALE -> out.writeScale(raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.ORTHO -> out.writeOrtho(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
case GLCommand.FRUSTUM -> out.writeFrustum(raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble(), raw.readDouble());
// Matrix commands
case GLCommand.MULT_MATRIX -> { out.writeMultMatrix(raw.readMatrix4f(tempMatrix)); }
case GLCommand.LOAD_MATRIX -> { out.writeLoadMatrix(raw.readMatrix4f(tempMatrix)); }
// FloatBuffer commands (inline 4 floats max)
case GLCommand.FOG -> {
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeFog(pname, fb);
}
case GLCommand.LIGHT -> {
final int light = raw.readInt();
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeLight(light, pname, fb);
}
case GLCommand.LIGHT_MODEL -> {
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeLightModel(pname, fb);
}
case GLCommand.MATERIAL -> {
final int face = raw.readInt();
final int pname = raw.readInt();
final int count = raw.readInt();
final FloatBuffer fb = FloatBuffer.allocate(count);
for (int i = 0; i < count; i++) fb.put(raw.readFloat());
fb.flip();
out.writeMaterial(face, pname, fb);
}
// Clip plane command (4 doubles)
case GLCommand.CLIP_PLANE -> {
final int plane = raw.readInt();
final double a = raw.readDouble();
final double b = raw.readDouble();
final double c = raw.readDouble();
final double d = raw.readDouble();
out.writeClipPlane(plane, a, b, c, d);
}
// Draw/call commands
case GLCommand.DRAW_RANGE -> out.writeDrawRange(raw.readInt());
case GLCommand.CALL_LIST -> out.writeCallList(raw.readInt());
case GLCommand.BIND_VBO -> out.writeBindVBO(raw.readInt());
case GLCommand.BIND_VAO -> out.writeBindVAO(raw.readInt());
// Complex object reference - transfer the object to output buffer
case GLCommand.COMPLEX_REF -> {
final int index = raw.readInt();
final Object obj = raw.getComplexObject(index);
out.writeComplexRef((DisplayListCommand) obj);
}
default -> GLStateManager.LOGGER.warn("[CommandBufferProcessor] Unknown opcode in buffer copy: {}", opcode);
}
}
}
@@ -4,376 +4,698 @@ import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatchBuilder;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawCapture;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.*;
/**
* Records GL commands to a CommandBuffer during display list compilation.
*/
public final class CommandRecorder {
private final CommandBuffer buffer;
private static final int DEFAULT_CAPACITY = 2048; // Guess at typical display list size
private ByteBuffer buffer;
private long basePointer;
private long writePointer;
private final ArrayList<DisplayListCommand> complexObjects = new ArrayList<>();
private final IndexedDrawBatchBuilder indexedDraws = new IndexedDrawBatchBuilder();
private int commandCount;
public CommandRecorder() {
this.buffer = new CommandBuffer();
this.commandCount = 0;
this(DEFAULT_CAPACITY);
}
public CommandBuffer getBuffer() {
return buffer;
public CommandRecorder(int initialCapacity) {
this.buffer = memAlloc(initialCapacity);
this.basePointer = memAddress0(buffer);
this.writePointer = basePointer;
}
public int getCommandCount() {
return commandCount;
// === Low-level write operations ===
private void writeInt(int value) {
memPutInt(writePointer, value);
writePointer += 4;
}
private void writeLong(long value) {
memPutLong(writePointer, value);
writePointer += 8;
}
private void writeFloat(float value) {
memPutFloat(writePointer, value);
writePointer += 4;
}
private void writeDouble(double value) {
memPutDouble(writePointer, value);
writePointer += 8;
}
private void ensureCapacity(int needed) {
final int size = size();
final int remaining = buffer.capacity() - size;
if (remaining < needed) {
final int newCapacity = Math.max(buffer.capacity() * 2, buffer.capacity() + needed);
buffer = memRealloc(buffer, newCapacity);
basePointer = memAddress0(buffer);
writePointer = basePointer + size;
}
}
public IndexedDrawBatchBuilder getIndexedDraws() {
return indexedDraws;
}
public void recordIndexedDrawCapture(IndexedDrawCapture capture) {
public void writeIndexedDrawCapture(IndexedDrawCapture capture) {
indexedDraws.add(capture);
recordComplexCommand(capture.placeholder);
writeComplexCommand(capture.placeholder);
}
public void free() {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
c.freeBuffers();
}
buffer.free();
}
// ==================== Recording Methods ====================
// === Single int commands ===
public void recordEnable(int cap) {
buffer.writeEnable(cap);
commandCount++;
public void writeEnable(int cap) {
ensureCapacity(8);
writeInt(GLCommand.ENABLE);
writeInt(cap);
}
public void recordDisable(int cap) {
buffer.writeDisable(cap);
commandCount++;
public void writeDisable(int cap) {
ensureCapacity(8);
writeInt(GLCommand.DISABLE);
writeInt(cap);
}
public void recordClear(int mask) {
buffer.writeClear(mask);
commandCount++;
public void writeClear(int mask) {
ensureCapacity(8);
writeInt(GLCommand.CLEAR);
writeInt(mask);
}
public void recordClearColor(float r, float g, float b, float a) {
buffer.writeClearColor(r, g, b, a);
commandCount++;
public void writeClearStencil(int s) {
ensureCapacity(8);
writeInt(GLCommand.CLEAR_STENCIL);
writeInt(s);
}
public void recordClearDepth(double depth) {
buffer.writeClearDepth(depth);
commandCount++;
public void writeCullFace(int mode) {
ensureCapacity(8);
writeInt(GLCommand.CULL_FACE);
writeInt(mode);
}
public void recordBlendColor(float r, float g, float b, float a) {
buffer.writeBlendColor(r, g, b, a);
commandCount++;
public void writeDepthFunc(int func) {
ensureCapacity(8);
writeInt(GLCommand.DEPTH_FUNC);
writeInt(func);
}
public void recordClearStencil(int s) {
buffer.writeClearStencil(s);
commandCount++;
public void writeShadeModel(int mode) {
ensureCapacity(8);
writeInt(GLCommand.SHADE_MODEL);
writeInt(mode);
}
public void recordColor(float r, float g, float b, float a) {
buffer.writeColor(r, g, b, a);
commandCount++;
public void writeLogicOp(int opcode) {
ensureCapacity(8);
writeInt(GLCommand.LOGIC_OP);
writeInt(opcode);
}
public void recordColorMask(boolean r, boolean g, boolean b, boolean a) {
buffer.writeColorMask(r, g, b, a);
commandCount++;
public void writeMatrixMode(int mode) {
ensureCapacity(8);
writeInt(GLCommand.MATRIX_MODE);
writeInt(mode);
}
public void recordDepthMask(boolean flag) {
buffer.writeDepthMask(flag);
commandCount++;
public void writeActiveTexture(int texture) {
ensureCapacity(8);
writeInt(GLCommand.ACTIVE_TEXTURE);
writeInt(texture);
}
public void recordFrontFace(int mode) {
buffer.writeFrontFace(mode);
commandCount++;
public void writeUseProgram(int program) {
ensureCapacity(8);
writeInt(GLCommand.USE_PROGRAM);
writeInt(program);
}
public void recordDepthFunc(int func) {
buffer.writeDepthFunc(func);
commandCount++;
public void writePushAttrib(int mask) {
ensureCapacity(8);
writeInt(GLCommand.PUSH_ATTRIB);
writeInt(mask);
}
public void recordBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
buffer.writeBlendFunc(srcRgb, dstRgb, srcAlpha, dstAlpha);
commandCount++;
public void writePopAttrib() {
ensureCapacity(8);
writeInt(GLCommand.POP_ATTRIB);
writeInt(0); // unused padding
}
public void recordAlphaFunc(int func, float ref) {
buffer.writeAlphaFunc(func, ref);
commandCount++;
public void writeLoadIdentity() {
ensureCapacity(4);
writeInt(GLCommand.LOAD_IDENTITY);
}
public void recordCullFace(int mode) {
buffer.writeCullFace(mode);
commandCount++;
public void writePushMatrix() {
ensureCapacity(4);
writeInt(GLCommand.PUSH_MATRIX);
}
public void recordShadeModel(int mode) {
buffer.writeShadeModel(mode);
commandCount++;
public void writePopMatrix() {
ensureCapacity(4);
writeInt(GLCommand.POP_MATRIX);
}
public void recordBindTexture(int target, int texture) {
buffer.writeBindTexture(target, texture);
commandCount++;
public void writeStencilMask(int mask) {
ensureCapacity(8);
writeInt(GLCommand.STENCIL_MASK);
writeInt(mask);
}
public void recordTexParameteri(int target, int pname, int param) {
buffer.writeTexParameteri(target, pname, param);
commandCount++;
public void writeDepthMask(boolean flag) {
ensureCapacity(8);
writeInt(GLCommand.DEPTH_MASK);
writeInt(flag ? 1 : 0);
}
public void recordTexParameterf(int target, int pname, float param) {
buffer.writeTexParameterf(target, pname, param);
commandCount++;
public void writeFrontFace(int mode) {
ensureCapacity(8);
writeInt(GLCommand.FRONT_FACE);
writeInt(mode);
}
public void recordMatrixMode(int mode) {
buffer.writeMatrixMode(mode);
commandCount++;
}
public void recordLoadIdentity() {
buffer.writeLoadIdentity();
commandCount++;
}
public void recordPushMatrix() {
buffer.writePushMatrix();
commandCount++;
}
public void recordPopMatrix() {
buffer.writePopMatrix();
commandCount++;
}
public void recordMultMatrix(Matrix4f matrix) {
buffer.writeMultMatrix(matrix);
commandCount++;
}
// === Two int commands ===
public void recordLoadMatrix(Matrix4f matrix) {
buffer.writeLoadMatrix(matrix);
commandCount++;
public void writeBindTexture(int target, int texture) {
ensureCapacity(12);
writeInt(GLCommand.BIND_TEXTURE);
writeInt(target);
writeInt(texture);
}
public void recordOrtho(double left, double right, double bottom, double top, double zNear, double zFar) {
buffer.writeOrtho(left, right, bottom, top, zNear, zFar);
commandCount++;
public void writePolygonMode(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_MODE);
writeInt(face);
writeInt(mode);
}
public void recordFrustum(double left, double right, double bottom, double top, double zNear, double zFar) {
buffer.writeFrustum(left, right, bottom, top, zNear, zFar);
commandCount++;
public void writeColorMaterial(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.COLOR_MATERIAL);
writeInt(face);
writeInt(mode);
}
public void recordViewport(int x, int y, int width, int height) {
buffer.writeViewport(x, y, width, height);
commandCount++;
public void writeLineStipple(int factor, int pattern) {
ensureCapacity(12);
writeInt(GLCommand.LINE_STIPPLE);
writeInt(factor);
writeInt(pattern);
}
public void recordScissor(int x, int y, int width, int height) {
buffer.writeScissor(x, y, width, height);
commandCount++;
public void writeStencilMaskSeparate(int face, int mask) {
ensureCapacity(12);
writeInt(GLCommand.STENCIL_MASK_SEPARATE);
writeInt(face);
writeInt(mask);
}
public void recordPointSize(float size) {
buffer.writePointSize(size);
commandCount++;
public void writeFogi(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.FOGI);
writeInt(pname);
writeInt(param);
}
public void recordLineWidth(float width) {
buffer.writeLineWidth(width);
commandCount++;
public void writeHint(int target, int mode) {
ensureCapacity(12);
writeInt(GLCommand.HINT);
writeInt(target);
writeInt(mode);
}
public void recordLineStipple(int factor, int pattern) {
buffer.writeLineStipple(factor, pattern);
commandCount++;
}
// === Three int commands ===
public void recordPolygonOffset(float factor, float units) {
buffer.writePolygonOffset(factor, units);
commandCount++;
public void writeStencilFunc(int func, int ref, int mask) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_FUNC);
writeInt(func);
writeInt(ref);
writeInt(mask);
}
public void recordPolygonMode(int face, int mode) {
buffer.writePolygonMode(face, mode);
commandCount++;
public void writeStencilOp(int fail, int zfail, int zpass) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_OP);
writeInt(fail);
writeInt(zfail);
writeInt(zpass);
}
public void recordColorMaterial(int face, int mode) {
buffer.writeColorMaterial(face, mode);
commandCount++;
public void writeTexParameteri(int target, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.TEX_PARAMETERI);
writeInt(target);
writeInt(pname);
writeInt(param);
}
public void recordLogicOp(int opcode) {
buffer.writeLogicOp(opcode);
commandCount++;
}
// === Four int commands ===
public void recordActiveTexture(int texture) {
buffer.writeActiveTexture(texture);
commandCount++;
public void writeViewport(int x, int y, int width, int height) {
ensureCapacity(20);
writeInt(GLCommand.VIEWPORT);
writeInt(x);
writeInt(y);
writeInt(width);
writeInt(height);
}
public void recordUseProgram(int program) {
buffer.writeUseProgram(program);
commandCount++;
public void writeScissor(int x, int y, int width, int height) {
ensureCapacity(20);
writeInt(GLCommand.SCISSOR);
writeInt(x);
writeInt(y);
writeInt(width);
writeInt(height);
}
public void recordPushAttrib(int mask) {
buffer.writePushAttrib(mask);
commandCount++;
public void writeBlendFunc(int srcRgb, int dstRgb, int srcAlpha, int dstAlpha) {
ensureCapacity(20);
writeInt(GLCommand.BLEND_FUNC);
writeInt(srcRgb);
writeInt(dstRgb);
writeInt(srcAlpha);
writeInt(dstAlpha);
}
public void recordPopAttrib() {
buffer.writePopAttrib();
commandCount++;
public void writeColorMask(boolean red, boolean green, boolean blue, boolean alpha) {
ensureCapacity(20);
writeInt(GLCommand.COLOR_MASK);
writeInt(red ? 1 : 0);
writeInt(green ? 1 : 0);
writeInt(blue ? 1 : 0);
writeInt(alpha ? 1 : 0);
}
public void recordFogf(int pname, float param) {
buffer.writeFogf(pname, param);
commandCount++;
public void writeStencilFuncSeparate(int face, int func, int ref, int mask) {
ensureCapacity(20);
writeInt(GLCommand.STENCIL_FUNC_SEPARATE);
writeInt(face);
writeInt(func);
writeInt(ref);
writeInt(mask);
}
public void recordFogi(int pname, int param) {
buffer.writeFogi(pname, param);
commandCount++;
public void writeStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
ensureCapacity(20);
writeInt(GLCommand.STENCIL_OP_SEPARATE);
writeInt(face);
writeInt(sfail);
writeInt(dpfail);
writeInt(dppass);
}
public void recordHint(int target, int mode) {
buffer.writeHint(target, mode);
commandCount++;
}
// === Float commands ===
public void recordFog(int pname, FloatBuffer params) {
buffer.writeFog(pname, params);
commandCount++;
public void writePointSize(float size) {
ensureCapacity(8);
writeInt(GLCommand.POINT_SIZE);
writeFloat(size);
}
public void recordLightf(int light, int pname, float param) {
buffer.writeLightf(light, pname, param);
commandCount++;
public void writeLineWidth(float width) {
ensureCapacity(8);
writeInt(GLCommand.LINE_WIDTH);
writeFloat(width);
}
public void recordLighti(int light, int pname, int param) {
buffer.writeLighti(light, pname, param);
commandCount++;
public void writePolygonOffset(float factor, float units) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_OFFSET);
writeFloat(factor);
writeFloat(units);
}
public void recordLight(int light, int pname, FloatBuffer params) {
buffer.writeLight(light, pname, params);
commandCount++;
public void writeColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void recordLightModelf(int pname, float param) {
buffer.writeLightModelf(pname, param);
commandCount++;
public void writeClearColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.CLEAR_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void recordLightModeli(int pname, int param) {
buffer.writeLightModeli(pname, param);
commandCount++;
public void writeClearDepth(double depth) {
ensureCapacity(12);
writeInt(GLCommand.CLEAR_DEPTH);
writeDouble(depth);
}
public void recordLightModel(int pname, FloatBuffer params) {
buffer.writeLightModel(pname, params);
commandCount++;
public void writeBlendColor(float r, float g, float b, float a) {
ensureCapacity(20);
writeInt(GLCommand.BLEND_COLOR);
writeFloat(r);
writeFloat(g);
writeFloat(b);
writeFloat(a);
}
public void recordMaterialf(int face, int pname, float val) {
buffer.writeMaterialf(face, pname, val);
commandCount++;
}
// === Mixed int+float commands ===
public void recordMaterial(int face, int pname, FloatBuffer params) {
buffer.writeMaterial(face, pname, params);
commandCount++;
public void writeAlphaFunc(int func, float ref) {
ensureCapacity(12);
writeInt(GLCommand.ALPHA_FUNC);
writeInt(func);
writeFloat(ref);
}
public void recordClipPlane(int plane, double a, double b, double c, double d) {
buffer.writeClipPlane(plane, a, b, c, d);
commandCount++;
public void writeFogf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.FOGF);
writeInt(pname);
writeFloat(param);
}
public void recordStencilFunc(int func, int ref, int mask) {
buffer.writeStencilFunc(func, ref, mask);
commandCount++;
public void writeLightf(int light, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTF);
writeInt(light);
writeInt(pname);
writeFloat(param);
}
public void recordStencilFuncSeparate(int face, int func, int ref, int mask) {
buffer.writeStencilFuncSeparate(face, func, ref, mask);
commandCount++;
public void writeLightModelf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELF);
writeInt(pname);
writeFloat(param);
}
public void recordStencilOp(int fail, int zfail, int zpass) {
buffer.writeStencilOp(fail, zfail, zpass);
commandCount++;
public void writeLighti(int light, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTI);
writeInt(light);
writeInt(pname);
writeInt(param);
}
public void recordStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
buffer.writeStencilOpSeparate(face, sfail, dpfail, dppass);
commandCount++;
public void writeLightModeli(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELI);
writeInt(pname);
writeInt(param);
}
public void recordStencilMask(int mask) {
buffer.writeStencilMask(mask);
commandCount++;
public void writeMaterialf(int face, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.MATERIALF);
writeInt(face);
writeInt(pname);
writeFloat(param);
}
public void recordStencilMaskSeparate(int face, int mask) {
buffer.writeStencilMaskSeparate(face, mask);
commandCount++;
}
public void recordCallList(int listId) {
buffer.writeCallList(listId);
commandCount++;
}
public void recordDrawBuffer(int mode) {
buffer.writeDrawBuffer(mode);
commandCount++;
public void writeTexParameterf(int target, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.TEX_PARAMETERF);
writeInt(target);
writeInt(pname);
writeFloat(param);
}
// === Transformation commands ===
public void writeTranslate(Vector3f translationVector) {
ensureCapacity(16);
writeInt(GLCommand.TRANSLATE);
writeFloat(translationVector.x);
writeFloat(translationVector.y);
writeFloat(translationVector.z);
}
public void writeScale(Vector3f scaleVector) {
ensureCapacity(16);
writeInt(GLCommand.SCALE);
writeFloat(scaleVector.x);
writeFloat(scaleVector.y);
writeFloat(scaleVector.z);
}
// === Matrix commands ===
public void writeMultMatrix(Matrix4f matrix) {
ensureCapacity(68);
writeInt(GLCommand.MULT_MATRIX);
writeMatrix4f(matrix);
}
public void writeLoadMatrix(Matrix4f matrix) {
ensureCapacity(68);
writeInt(GLCommand.LOAD_MATRIX);
writeMatrix4f(matrix);
}
/**
* Write a Matrix4f to the buffer in column-major order (OpenGL convention).
* Uses accessor methods instead of getToAddress() to avoid JOML MemUtil issues.
*/
private void writeMatrix4f(Matrix4f m) {
// Column 0
writeFloat(m.m00()); writeFloat(m.m01()); writeFloat(m.m02()); writeFloat(m.m03());
// Column 1
writeFloat(m.m10()); writeFloat(m.m11()); writeFloat(m.m12()); writeFloat(m.m13());
// Column 2
writeFloat(m.m20()); writeFloat(m.m21()); writeFloat(m.m22()); writeFloat(m.m23());
// Column 3
writeFloat(m.m30()); writeFloat(m.m31()); writeFloat(m.m32()); writeFloat(m.m33());
}
// === Buffer commands (inline 4 floats) ===
public void writeFog(int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(12 + count * 4);
writeInt(GLCommand.FOG);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeLight(int light, int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(16 + count * 4);
writeInt(GLCommand.LIGHT);
writeInt(light);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeLightModel(int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(12 + count * 4);
writeInt(GLCommand.LIGHT_MODEL);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeMaterial(int face, int pname, FloatBuffer params) {
final int count = Math.min(params.remaining(), 4);
ensureCapacity(16 + count * 4);
writeInt(GLCommand.MATERIAL);
writeInt(face);
writeInt(pname);
writeInt(count);
final int pos = params.position();
for (int i = 0; i < count; i++) {
writeFloat(params.get(pos + i));
}
}
public void writeClipPlane(int plane, double a, double b, double c, double d) {
ensureCapacity(40);
writeInt(GLCommand.CLIP_PLANE);
writeInt(plane);
writeDouble(a);
writeDouble(b);
writeDouble(c);
writeDouble(d);
}
// === Draw commands ===
public void writeDrawRange(int vboIndex) {
ensureCapacity(8);
writeInt(GLCommand.DRAW_RANGE);
writeInt(vboIndex);
}
/**
* Write a DRAW_RANGE_RESTORE command that draws a VBO range and restores GL current state after.
* Used for immediate mode VBOs to restore GL_CURRENT_COLOR, GL_CURRENT_NORMAL, GL_CURRENT_TEXTURE_COORDS.
*
* @param vboIndex Index into ownedVbos array
*/
public void writeDrawRangeRestore(
int vboIndex, AccumulatedDraw.RestoreData restoreData
) {
ensureCapacity(28);
writeInt(GLCommand.DRAW_RANGE_RESTORE);
writeInt(vboIndex);
writeInt(restoreData.flags);
// Last color (4 bytes)
writeInt(restoreData.lastColor);
// Last normal (4 bytes)
writeInt(restoreData.lastNormal);
// Last texcoord (2 floats = 8 bytes)
writeFloat(restoreData.lastTexCoordU);
writeFloat(restoreData.lastTexCoordV);
}
public void writeCallList(int listId) {
ensureCapacity(8);
writeInt(GLCommand.CALL_LIST);
writeInt(listId);
}
public void writeBindVBO(int vbo) {
ensureCapacity(8);
writeInt(GLCommand.BIND_VBO);
writeInt(vbo);
}
public void writeBindVAO(int vao) {
ensureCapacity(8);
writeInt(GLCommand.BIND_VAO);
writeInt(vao);
}
public void writeDrawBuffer(int mode) {
ensureCapacity(8);
writeInt(GLCommand.DRAW_BUFFER);
writeInt(mode);
}
private static final int MAX_DRAW_BUFFERS = 8;
// cmd (4) + count (4) + buffers (4 * MAX_DRAW_BUFFERS)
private static final int DRAW_BUFFERS_SIZE = 4 + 4 + 4 * MAX_DRAW_BUFFERS;
public void writeDrawBuffers(int count, int buffer) {
ensureCapacity(DRAW_BUFFERS_SIZE);
writeInt(GLCommand.DRAW_BUFFERS);
writeInt(count);
writeInt(buffer);
for (int i = 1; i < MAX_DRAW_BUFFERS; i++) {
writeInt(0);
}
}
public void writeDrawBuffers(int count, IntBuffer bufs) {
ensureCapacity(DRAW_BUFFERS_SIZE);
writeInt(GLCommand.DRAW_BUFFERS);
writeInt(count);
final int pos = bufs.position();
for (int i = 0; i < count && i < MAX_DRAW_BUFFERS; i++) {
writeInt(bufs.get(pos + i));
}
for (int i = count; i < MAX_DRAW_BUFFERS; i++) {
writeInt(0);
}
}
// === Complex object reference ===
public void writeComplexCommand(DisplayListCommand cmd) {
ensureCapacity(8);
final int index = complexObjects.size();
complexObjects.add(cmd);
writeInt(GLCommand.COMPLEX_REF);
writeInt(index);
}
// === Lifecycle ===
public int size() {
return (int) (writePointer - basePointer);
}
public void recordDrawBuffers(int count, int buf) {
buffer.writeDrawBuffers(count, buf);
commandCount++;
public void delete() {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
c.freeBuffers();
}
for (DisplayListCommand cmd : complexObjects) {
cmd.delete();
}
complexObjects.clear();
if (buffer != null) {
memFree(buffer);
buffer = null;
}
}
/**
* Deletes every allocated object & returns a ByteBuffer with the capacity equal to its limit.
* This CommandRecorder object cannot be used after calling this anymore
*/
public ByteBuffer finish() {
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
c.freeBuffers();
}
final int size = size();
buffer.limit(size);
final ByteBuffer out = memRealloc(buffer, size);
buffer = null;
complexObjects.clear();
return out;
}
public void recordDrawBuffers(int count, IntBuffer bufs) {
buffer.writeDrawBuffers(count, bufs);
commandCount++;
public DisplayListCommand[] getComplexObjects() {
return complexObjects.toArray(new DisplayListCommand[complexObjects.size()]);
}
public void recordBindVBO(int vbo) {
buffer.writeBindVBO(vbo);
commandCount++;
public DisplayListCommand getComplexObject(int index) {
return complexObjects.get(index);
}
public void recordBindVAO(int vao) {
buffer.writeBindVAO(vao);
commandCount++;
public boolean isEmpty() {
return size() == 0;
}
public void recordComplexCommand(DisplayListCommand cmd) {
buffer.writeComplexRef(cmd);
commandCount++;
/**
* Clear the buffer for reuse. Does NOT free memory.
*/
public void clear() {
writePointer = basePointer;
complexObjects.clear();
}
}
@@ -32,11 +32,11 @@ public final class CompiledDisplayList {
public static final CompiledDisplayList EMPTY = new CompiledDisplayList(null, null, null, Collections.emptyList());
private final ByteBuffer commandBuffer; // Off-heap command storage, must be freed
private final Object[] complexObjects; // Complex commands (TexImage2D, etc.)
private final DisplayListCommand[] complexObjects; // Complex commands (TexImage2D, etc.)
private final DisplayListVBO ownedVbos; // GPU resources referenced by index
private final List<IndexedDrawBatch> indexedBatches; // Shared VAO/VBO/EBO triples per layout group
public CompiledDisplayList(ByteBuffer commandBuffer, Object[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches) {
public CompiledDisplayList(ByteBuffer commandBuffer, DisplayListCommand[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches) {
this.commandBuffer = commandBuffer;
this.complexObjects = complexObjects;
this.ownedVbos = ownedVbos;
@@ -47,8 +47,12 @@ public final class CompiledDisplayList {
* Render this display list by executing all commands.
*/
public void render() {
render(-1);
}
public void render(int list) {
if (commandBuffer != null && commandBuffer.limit() > 0) {
CommandBufferExecutor.execute(commandBuffer, complexObjects, ownedVbos);
CommandBufferExecutor.execute(commandBuffer, complexObjects, ownedVbos, list, this);
}
}
@@ -57,10 +61,8 @@ public final class CompiledDisplayList {
*/
public void delete() {
if (complexObjects != null) {
for (Object obj : complexObjects) {
if (obj instanceof DisplayListCommand cmd) {
cmd.delete();
}
for (DisplayListCommand cmd : complexObjects) {
cmd.delete();
}
}
@@ -92,7 +94,7 @@ public final class CompiledDisplayList {
/**
* Get complex objects for inlining into another display list (nested lists).
*/
public Object[] getComplexObjects() {
public DisplayListCommand[] getComplexObjects() {
return complexObjects;
}
@@ -144,8 +146,20 @@ public final class CompiledDisplayList {
return opcodes;
}
public String getCompiledDisplayListString(int listId, StackTraceElement[] stackTrace) {
return DisplayListManager.getCompiledDisplayListString(listId, this, stackTrace);
}
public String toString(int listId, StackTraceElement[] stackTrace) {
return getCompiledDisplayListString(listId, stackTrace);
}
public String toString(int listId) {
return toString(listId, null);
}
@Override
public String toString() {
return DisplayListManager.getCompiledDisplayListString(0, this, null);
return toString(0);
}
}
@@ -23,6 +23,10 @@ public final class DisplayListVBO {
return vbos;
}
public SubVBO getVBO(int index) {
return vbos[index];
}
public void render(int index) {
vbos[index].render();
}
@@ -41,6 +45,18 @@ public final class DisplayListVBO {
this.count = count;
}
public int getStart() {
return start;
}
public int getCount() {
return count;
}
public int getDrawMode() {
return drawMode;
}
public void delete() {
vao.delete();
}
@@ -68,7 +68,7 @@ public final class DisplayListVBOBuilder {
allBuffers.addAll(drawBuffers);
int size = 0;
for (ByteBuffer buffer : drawBuffers) {
size += buffer.remaining();
size += buffer.limit();
}
vertexCount = format.getVertexCount(size);
}
@@ -74,10 +74,7 @@ public final class GLCommand {
// === Double commands ===
public static final int TRANSLATE = 70; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int ROTATE = 71; // [cmd:4][mode:4][angle:8d][x:8d][y:8d][z:8d] = 40 bytes
public static final int SCALE = 72; // [cmd:4][mode:4][x:8d][y:8d][z:8d] = 32 bytes
public static final int ORTHO = 73; // [cmd:4][left:8d][right:8d][bottom:8d][top:8d][zNear:8d][zFar:8d] = 52 bytes
public static final int FRUSTUM = 74; // [cmd:4][left:8d][right:8d][bottom:8d][top:8d][zNear:8d][zFar:8d] = 52 bytes
public static final int CLEAR_DEPTH = 75; // [cmd:4][depth:8d] = 12 bytes
// === Matrix commands (72 bytes) ===
@@ -167,10 +164,7 @@ public final class GLCommand {
case MATERIALF -> "MATERIALF";
case TEX_PARAMETERF -> "TEX_PARAMETERF";
case TRANSLATE -> "TRANSLATE";
case ROTATE -> "ROTATE";
case SCALE -> "SCALE";
case ORTHO -> "ORTHO";
case FRUSTUM -> "FRUSTUM";
case CLEAR_DEPTH -> "CLEAR_DEPTH";
case MULT_MATRIX -> "MULT_MATRIX";
case LOAD_MATRIX -> "LOAD_MATRIX";
@@ -235,10 +229,8 @@ public final class GLCommand {
case GLCommand.DRAW_BUFFERS -> 40;
// Double commands
case GLCommand.TRANSLATE, GLCommand.SCALE -> 28; // cmd + 3 doubles
case GLCommand.TRANSLATE, GLCommand.SCALE -> 16; // cmd + 3 floats
case GLCommand.CLEAR_DEPTH -> 12; // cmd + 1 double
case GLCommand.ROTATE -> 36; // cmd + 4 doubles
case GLCommand.ORTHO, GLCommand.FRUSTUM -> 52; // cmd + 6 doubles
// Matrix commands
case GLCommand.MULT_MATRIX, GLCommand.LOAD_MATRIX -> 68; // cmd + 16 floats
@@ -1,43 +1,56 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.CallbackTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizon.gtnhlib.client.renderer.TessellatorManager;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* Records immediate mode GL calls (glBegin/glEnd/glVertex) via {@link DirectTessellator}.
* Used both during display list compilation and for live immediate mode emulation in core profile.
*/
public final class ImmediateModeRecorder {
private static final DirectTessellator tessellator = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
private static final CallbackTessellator tessellator = new CallbackTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
/** Separate tessellator for the splash thread. Nulled after splash completes. */
private static DirectTessellator splashTessellator;
private record ThreadTessellator(Thread thread, DirectTessellator tessellator) {}
private static final Queue<ThreadTessellator> threadTessellators = new ConcurrentLinkedQueue<>();
private static final ThreadLocal<DirectTessellator> threadTessellator = ThreadLocal.withInitial(() -> {
final DirectTessellator t = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
threadTessellators.add(new ThreadTessellator(Thread.currentThread(), t));
cleanupOrphanTessellators();
return t;
});
private ImmediateModeRecorder() {
}
public static void initSplashTessellator() {
splashTessellator = new DirectTessellator(TessellatorManager.DEFAULT_BUFFER_SIZE);
public static int getThreadTessellatorCount() {
return threadTessellators.size();
}
public static void destroySplashTessellator() {
splashTessellator = null;
public static synchronized void cleanupOrphanTessellators() {
threadTessellators.removeIf(tt -> {
if (tt.thread().isAlive()) return false;
tt.tessellator().delete();
return true;
});
}
private static DirectTessellator tessellator() {
final DirectTessellator splash = splashTessellator;
if (splash != null && Thread.currentThread() != GLStateManager.getMainThread()) {
return splash;
if (Thread.currentThread() == GLStateManager.getMainThread()) {
return tessellator;
}
return tessellator;
return threadTessellator.get();
}
public static DirectTessellator getInternalTessellator() {
@@ -291,7 +304,39 @@ public final class ImmediateModeRecorder {
}
private static float readComponent(AttribReader r, int base, int component) {
return VertexAttribState.Attrib.readComponent(r.type, r.normalized, r.data, base, component);
return VAOManager.Attrib.readComponent(r.type, r.normalized, r.data, base, component);
}
public static float readComponent(int type, boolean normalized, ByteBuffer buf, int base, int component) {
return switch (type) {
case GL11.GL_FLOAT -> buf.getFloat(base + component * 4);
case GL11.GL_DOUBLE -> (float) buf.getDouble(base + component * 8);
case GL11.GL_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? v / (float) Integer.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? (v & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL : (float) (v & 0xFFFFFFFFL);
}
case GL11.GL_SHORT -> {
final short v = buf.getShort(base + component * 2);
yield normalized ? v / (float) Short.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_SHORT -> {
final int v = buf.getShort(base + component * 2) & 0xFFFF;
yield normalized ? v / (float) 0xFFFF : (float) v;
}
case GL11.GL_BYTE -> {
final byte v = buf.get(base + component);
yield normalized ? v / 127.0f : (float) v;
}
case GL11.GL_UNSIGNED_BYTE -> {
final int v = buf.get(base + component) & 0xFF;
yield normalized ? v / 255.0f : (float) v;
}
default -> 0.0f;
};
}
}
@@ -1,6 +1,6 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import java.util.Arrays;
@@ -24,7 +24,7 @@ public final class AttribLayoutKey {
int base = 0;
int maxAlign = 1;
for (int i = 0; i < n; i++) {
final int typeSize = VertexAttribState.Attrib.glTypeSizeBytes(types[i]);
final int typeSize = VAOManager.Attrib.glTypeSizeBytes(types[i]);
if (typeSize > maxAlign) maxAlign = typeSize;
base = (base + typeSize - 1) & -typeSize;
off[i] = base;
@@ -3,14 +3,12 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.AttribSnapshot;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Set;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetLong;
@@ -52,51 +50,69 @@ public final class IndexedDrawCapture {
return freed;
}
private static final Set<String> WARN_ONCE = ConcurrentHashMap.newKeySet();
private static void warnOnce(String key, String fmt, Object... args) {
if (WARN_ONCE.add(key)) GLStateManager.LOGGER.warn(fmt, args);
}
public static IndexedDrawCapture create(int mode, int indicesCount, int srcIndexType, long indicesOffset, int eboId) {
if (indicesCount == 0) return null;
if (eboId == 0) {
warnOnce("no-ebo", "[IndexedDrawCapture] glDrawElements in display list with no EBO bound — skipping (mode={} count={} offset={})", mode, indicesCount, indicesOffset);
GLStateManager.warnOnce("no-ebo", "[IndexedDrawCapture] glDrawElements in display list with no EBO bound — skipping (mode={} count={} offset={})", mode, indicesCount, indicesOffset);
return null;
}
if (mode == GL11.GL_QUADS && (indicesCount % 4) != 0) {
warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 — skipping", indicesCount);
GLStateManager.warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 — skipping", indicesCount);
return null;
}
final int srcIndexSize = VertexAttribState.Attrib.glTypeSizeBytes(srcIndexType);
final int srcIndexSize = VAOManager.Attrib.glTypeSizeBytes(srcIndexType);
if (srcIndexSize <= 0) return null;
final int prevEBO = GLStateManager.getBoundEBO();
final int prevVBO = GLStateManager.getBoundVBO();
ByteBuffer eboSrc = null;
try {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
final int eboBufferSize = GLStateManager.glGetBufferParameteri(GL15.GL_ELEMENT_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (eboBufferSize <= 0) {
GLStateManager.warnOnce("ebo-size", "[IndexedDrawCapture] GL_BUFFER_SIZE={} for EBO {}", eboBufferSize, eboId);
return null;
}
final long eboReadSize = (long) indicesCount * srcIndexSize;
if (indicesOffset < 0 || indicesOffset + eboReadSize > eboBufferSize) {
GLStateManager.warnOnce("ebo-range", "[IndexedDrawCapture] EBO range {}+{} exceeds buffer size {} — skipping", indicesOffset, eboReadSize, eboBufferSize);
return null;
}
eboSrc = MemoryUtilities.memAlloc((int) eboReadSize);
GLStateManager.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesOffset, eboSrc);
return bake(mode, indicesCount, srcIndexType, MemoryUtilities.memAddress0(eboSrc));
} finally {
if (eboSrc != null) MemoryUtilities.memFree(eboSrc);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
}
}
public static IndexedDrawCapture createFromClientIndices(int mode, int indicesCount, int srcIndexType, long indexSrcAddr, long srcByteLength) {
if (indicesCount == 0) return null;
if (indexSrcAddr == 0L) return null;
if (mode == GL11.GL_QUADS && (indicesCount % 4) != 0) {
GLStateManager.warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 - skipping", indicesCount);
return null;
}
final int idxSize = VAOManager.Attrib.glTypeSizeBytes(srcIndexType);
if (idxSize <= 0) return null;
if ((long) indicesCount * idxSize > srcByteLength) {
GLStateManager.warnOnce("client-idx-range", "[IndexedDrawCapture] index count {} ({} bytes) exceeds client buffer {} bytes - skipping", indicesCount, (long) indicesCount * idxSize, srcByteLength);
return null;
}
return bake(mode, indicesCount, srcIndexType, indexSrcAddr);
}
private static IndexedDrawCapture bake(int mode, int indicesCount, int srcIndexType, long indexSrcAddr) {
AttribSnapshot snap = null;
ByteBuffer vertexData = null;
ByteBuffer indexData = null;
boolean success = false;
try {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
final int eboBufferSize = GLStateManager.glGetBufferParameteri(GL15.GL_ELEMENT_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (eboBufferSize <= 0) {
warnOnce("ebo-size", "[IndexedDrawCapture] GL_BUFFER_SIZE={} for EBO {}", eboBufferSize, eboId);
return null;
}
final long eboReadSize = (long) indicesCount * srcIndexSize;
if (indicesOffset < 0 || indicesOffset + eboReadSize > eboBufferSize) {
warnOnce("ebo-range", "[IndexedDrawCapture] EBO range {}+{} exceeds buffer size {} — skipping", indicesOffset, eboReadSize, eboBufferSize);
return null;
}
eboSrc = MemoryUtilities.memAlloc((int) eboReadSize);
GLStateManager.glGetBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesOffset, eboSrc);
final long eboSrcAddr = MemoryUtilities.memAddress0(eboSrc);
final long minMax = QuadConverter.scanMinMaxIndex(eboSrcAddr, srcIndexType, indicesCount);
final long minMax = QuadConverter.scanMinMaxIndex(indexSrcAddr, srcIndexType, indicesCount);
if (minMax == -1L) {
warnOnce("min-max", "[IndexedDrawCapture] failed to determine min/max vertex for indexType={}", srcIndexType);
GLStateManager.warnOnce("min-max", "[IndexedDrawCapture] failed to determine min/max vertex for indexType={}", srcIndexType);
return null;
}
final int minVtx = (int) (minMax & 0xFFFFFFFFL);
@@ -106,11 +122,11 @@ public final class IndexedDrawCapture {
snap = AttribSnapshot.snapshot(minVtx, vertexCount);
int enabledCount = 0;
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
if (snap.get(i) != null) enabledCount++;
}
if (enabledCount == 0) {
warnOnce("no-attribs", "[IndexedDrawCapture] no enabled attributes — skipping");
GLStateManager.warnOnce("no-attribs", "[IndexedDrawCapture] no enabled attributes — skipping");
return null;
}
final int[] locations = new int[enabledCount];
@@ -118,7 +134,7 @@ public final class IndexedDrawCapture {
final int[] types = new int[enabledCount];
final boolean[] normalized = new boolean[enabledCount];
int k = 0;
for (int i = 0; i < VertexAttribState.MAX_ATTRIBS; i++) {
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final AttribSnapshot.AttribDesc d = snap.get(i);
if (d == null) continue;
locations[k] = i;
@@ -137,6 +153,13 @@ public final class IndexedDrawCapture {
for (int a = 0; a < enabledCount; a++) {
final AttribSnapshot.AttribDesc d = snap.get(locations[a]);
final int relOffset = (int) (d.offset() - d.readBufferBaseOffset());
final long readStart = relOffset + (long) minVtx * d.effectiveStride();
final long readEnd = relOffset + (long) maxVtx * d.effectiveStride() + (long) d.size() * d.typeSizeBytes();
if (readStart < 0 || readEnd > d.readBuffer().capacity()) {
GLStateManager.warnOnce("vtx-oob", "[IndexedDrawCapture] attrib {} read extent {} exceeds snapshot {} bytes - skipping", d.location(), readEnd, d.readBuffer().capacity());
return null;
}
if (a == 0) {
sharedSrcBuffer = d.readBuffer();
sharedSrcStride = d.effectiveStride();
@@ -169,12 +192,12 @@ public final class IndexedDrawCapture {
final int quadCount = indicesCount / 4;
bakedIndexCount = quadCount * 6;
indexData = MemoryUtilities.memAlloc(bakedIndexCount * 4);
QuadConverter.triangulateQuads(eboSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, quadCount, minVtx);
QuadConverter.triangulateQuads(indexSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, quadCount, minVtx);
bakedDrawMode = GL11.GL_TRIANGLES;
} else {
bakedIndexCount = indicesCount;
indexData = MemoryUtilities.memAlloc(bakedIndexCount * 4);
QuadConverter.widenIndices(eboSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, indicesCount, minVtx);
QuadConverter.widenIndices(indexSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, indicesCount, minVtx);
bakedDrawMode = mode;
}
@@ -184,14 +207,11 @@ public final class IndexedDrawCapture {
success = true;
return out;
} finally {
if (eboSrc != null) MemoryUtilities.memFree(eboSrc);
if (snap != null) snap.free();
if (!success) {
if (vertexData != null) MemoryUtilities.memFree(vertexData);
if (indexData != null) MemoryUtilities.memFree(indexData);
}
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
}
}
@@ -0,0 +1,52 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAlloc;
final class PixelDataSnapshot {
private PixelDataSnapshot() {}
static @Nullable ByteBuffer copy(@Nullable ByteBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining()).order(ByteOrder.nativeOrder());
out.put(pixels);
out.flip();
pixels.position(pos);
return out;
}
static @Nullable ByteBuffer copy(@Nullable IntBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining() * 4).order(ByteOrder.nativeOrder());
out.asIntBuffer().put(pixels);
pixels.position(pos);
return out;
}
static @Nullable ByteBuffer copy(@Nullable FloatBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining() * 4).order(ByteOrder.nativeOrder());
out.asFloatBuffer().put(pixels);
pixels.position(pos);
return out;
}
static @Nullable ByteBuffer copy(@Nullable DoubleBuffer pixels) {
if (pixels == null) return null;
final int pos = pixels.position();
final ByteBuffer out = memAlloc(pixels.remaining() * 8).order(ByteOrder.nativeOrder());
out.asDoubleBuffer().put(pixels);
pixels.position(pos);
return out;
}
}
@@ -2,15 +2,17 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
/**
* Command: glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels)
* Specifies a two-dimensional texture image during display list playback.
@@ -24,92 +26,58 @@ public record TexImage2DCmd(
int border,
int format,
int type,
@Nullable byte[] pixelData
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
) implements DisplayListCommand {
/**
* Create command from IntBuffer
*/
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per int
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asIntBuffer().put(pixels);
pixels.position(pos); // Restore position
public TexImage2DCmd {
if (pixels == null) {
unpack = null;
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
}
/**
* Create command from FloatBuffer
*/
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per float
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asFloatBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
public static TexImage2DCmd fromIntBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from DoubleBuffer
*/
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 8; // 8 bytes per double
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asDoubleBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
public static TexImage2DCmd fromFloatBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from ByteBuffer
*/
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = limit - pos;
data = new byte[size];
pixels.get(data);
pixels.position(pos); // Restore position
}
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, data);
public static TexImage2DCmd fromDoubleBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexImage2DCmd fromByteBuffer(int target, int level, int internalformat, int width, int height, int border, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
return new TexImage2DCmd(target, level, internalformat, width, height, border, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@Override
public void execute() {
ByteBuffer buffer = null;
if (pixelData != null) {
buffer = ByteBuffer.allocateDirect(pixelData.length).order(ByteOrder.nativeOrder());
buffer.put(pixelData);
buffer.flip();
if (pixels == null) {
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, (ByteBuffer) null);
return;
}
GLStateManager.forcePixelUnpackState(unpack);
try {
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, pixels);
} finally {
GLStateManager.restorePixelUnpackState(unpack);
}
}
@Override
public void delete() {
if (pixels != null) {
memFree(pixels);
}
GLStateManager.glTexImage2D(target, level, internalformat, width, height, border, format, type, buffer);
}
@Override
public @NotNull String toString() {
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
return String.format("TexImage2D(target=%s, level=%d, internalformat=%s, %dx%d, border=%d, format=%s, type=%s, data=%s)",
String dataStr = pixels != null ? (pixels.remaining() + " bytes") : "null";
return String.format("TexImage2D(target=%s, level=%d, internalformat=%s, %dx%d, border=%d, format=%s, type=%s, data=%s, unpack=%s)",
GLDebug.getTextureTargetName(target), level, GLDebug.getTextureFormatName(internalformat),
width, height, border, GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
width, height, border, GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr, unpack);
}
}
@@ -2,15 +2,17 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
/**
* Command: glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels)
* Updates a portion of an existing two-dimensional texture image during display list playback.
@@ -24,92 +26,58 @@ public record TexSubImage2DCmd(
int height,
int format,
int type,
@Nullable byte[] pixelData
@Nullable ByteBuffer pixels,
@Nullable PixelUnpackState unpack
) implements DisplayListCommand {
/**
* Create command from IntBuffer
*/
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per int
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asIntBuffer().put(pixels);
pixels.position(pos); // Restore position
public TexSubImage2DCmd {
if (pixels == null) {
unpack = null;
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
}
/**
* Create command from FloatBuffer
*/
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 4; // 4 bytes per float
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asFloatBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
public static TexSubImage2DCmd fromIntBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable IntBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from DoubleBuffer
*/
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = (limit - pos) * 8; // 8 bytes per double
data = new byte[size];
ByteBuffer bb = ByteBuffer.wrap(data).order(ByteOrder.nativeOrder());
bb.asDoubleBuffer().put(pixels);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
public static TexSubImage2DCmd fromFloatBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable FloatBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
/**
* Create command from ByteBuffer
*/
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels) {
byte[] data = null;
if (pixels != null) {
int pos = pixels.position();
int limit = pixels.limit();
int size = limit - pos;
data = new byte[size];
pixels.get(data);
pixels.position(pos); // Restore position
}
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, data);
public static TexSubImage2DCmd fromDoubleBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable DoubleBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
public static TexSubImage2DCmd fromByteBuffer(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, @Nullable ByteBuffer pixels, PixelUnpackState unpack) {
return new TexSubImage2DCmd(target, level, xoffset, yoffset, width, height, format, type, PixelDataSnapshot.copy(pixels), unpack);
}
@Override
public void execute() {
ByteBuffer buffer = null;
if (pixelData != null) {
buffer = ByteBuffer.allocateDirect(pixelData.length).order(ByteOrder.nativeOrder());
buffer.put(pixelData);
buffer.flip();
if (pixels == null) {
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, (ByteBuffer) null);
return;
}
GLStateManager.forcePixelUnpackState(unpack);
try {
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, pixels);
} finally {
GLStateManager.restorePixelUnpackState(unpack);
}
}
@Override
public void delete() {
if (pixels != null) {
memFree(pixels);
}
GLStateManager.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, buffer);
}
@Override
public @NotNull String toString() {
String dataStr = pixelData != null ? (pixelData.length + " bytes") : "null";
return String.format("TexSubImage2D(target=%s, level=%d, offset=(%d,%d), %dx%d, format=%s, type=%s, data=%s)",
String dataStr = pixels != null ? (pixels.remaining() + " bytes") : "null";
return String.format("TexSubImage2D(target=%s, level=%d, offset=(%d,%d), %dx%d, format=%s, type=%s, data=%s, unpack=%s)",
GLDebug.getTextureTargetName(target), level, xoffset, yoffset, width, height,
GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr);
GLDebug.getTextureFormatName(format), GLDebug.getDataTypeName(type), dataStr, unpack);
}
}
@@ -46,6 +46,13 @@ public class GLSMRedirector {
private static final String ARBMultiTexture = "org/lwjgl/opengl/ARBMultitexture";
private static final String ARBShaderObjects = "org/lwjgl/opengl/ARBShaderObjects";
private static final String ARBInstancedArrays = "org/lwjgl/opengl/ARBInstancedArrays";
private static final String ARBDrawInstanced = "org/lwjgl/opengl/ARBDrawInstanced";
private static final String EXTDrawInstanced = "org/lwjgl/opengl/EXTDrawInstanced";
private static final String ARBBufferObject = "org/lwjgl/opengl/ARBBufferObject";
private static final String ARBVertexBufferObject = "org/lwjgl/opengl/ARBVertexBufferObject";
private static final String ARBBufferStorage = "org/lwjgl/opengl/ARBBufferStorage";
private static final String ARBCopyBuffer = "org/lwjgl/opengl/ARBCopyBuffer";
private static final String ARBMapBufferRange = "org/lwjgl/opengl/ARBMapBufferRange";
// Redirect VAO related calls from NHLib
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
@@ -126,6 +133,7 @@ public class GLSMRedirector {
.add("glFrustum")
.add("glGetBoolean")
.add("glGetFloat")
.add("glGetFloatv", "glGetFloat")
.add("glGetInteger")
.add("glGetLight")
.add("glGetMaterial")
@@ -282,11 +290,16 @@ public class GLSMRedirector {
.add("glBindBuffer")
.add("glDeleteBuffers")
.add("glBufferData")
.add("nglBufferData")
.add("glBufferSubData")
.add("nglBufferSubData")
.add("glMapBuffer")
.add("glUnmapBuffer")
.add("glGetBufferSubData")
.add("nglGetBufferSubData")
.add("glGetBufferParameteri")
.add("glGetBufferParameter")
.add("glGetBufferParameteriv")
.add("glIsBuffer");
final var gl20 = RedirectMap.newMap()
.add("glBlendEquationSeparate")
@@ -296,6 +309,7 @@ public class GLSMRedirector {
.add("glStencilOpSeparate")
.add("glUseProgram")
.add("glShaderSource")
.add("nglShaderSource")
.add("glLinkProgram")
.add("glDeleteProgram")
.add("glCreateShader")
@@ -321,6 +335,7 @@ public class GLSMRedirector {
.add("glUniformMatrix2")
.add("glUniformMatrix3")
.add("glUniformMatrix4")
.add("glUniformMatrix4fv", "glUniformMatrix4")
.add("glDeleteShader")
.add("glGetShaderi")
.add("glGetShaderInfoLog")
@@ -342,6 +357,7 @@ public class GLSMRedirector {
.add("glGenVertexArrays")
.add("glBindVertexArray")
.add("glDeleteVertexArrays")
.add("glIsVertexArray")
.add("glBindFramebuffer")
.add("glDeleteFramebuffers")
.add("glGenFramebuffers")
@@ -350,9 +366,13 @@ public class GLSMRedirector {
.add("glVertexAttribIPointer")
.add("glGenerateMipmap")
.add("glGetFramebufferAttachmentParameteri")
.add("glBlitFramebuffer");
.add("glBlitFramebuffer")
.add("glMapBufferRange")
.add("glFlushMappedBufferRange");
final var gl31 = RedirectMap.newMap()
.add("glDrawElementsInstanced");
.add("glDrawElementsInstanced")
.add("glDrawArraysInstanced")
.add("glCopyBufferSubData");
final var gl32 = RedirectMap.newMap()
.add("glFramebufferTexture");
final var gl33 = RedirectMap.newMap()
@@ -376,6 +396,10 @@ public class GLSMRedirector {
final var gl44 = RedirectMap.newMap()
.add("glBufferStorage")
.add("glClearTexImage");
final var gl45 = RedirectMap.newMap()
.add("glCreateBuffers")
.add("glNamedBufferData")
.add("glNamedBufferSubData");
// Merge all GL version maps into the flat lookup
glMethodRedirects.putAll(gl11);
@@ -391,6 +415,7 @@ public class GLSMRedirector {
glMethodRedirects.putAll(gl42);
glMethodRedirects.putAll(gl43);
glMethodRedirects.putAll(gl44);
glMethodRedirects.putAll(gl45);
// MINECRAFT
methodRedirects.put(OpenGlHelper, RedirectMap.newMap()
@@ -455,6 +480,39 @@ public class GLSMRedirector {
methodRedirects.put(ARBInstancedArrays, RedirectMap.newMap()
.add("glVertexAttribDivisorARB")
);
methodRedirects.put(ARBDrawInstanced, RedirectMap.newMap()
.add("glDrawArraysInstancedARB", "glDrawArraysInstanced")
.add("glDrawElementsInstancedARB", "glDrawElementsInstanced")
);
methodRedirects.put(EXTDrawInstanced, RedirectMap.newMap()
.add("glDrawArraysInstancedEXT", "glDrawArraysInstanced")
.add("glDrawElementsInstancedEXT", "glDrawElementsInstanced")
);
final var arbBufferObject = RedirectMap.newMap()
.add("glBindBufferARB", "glBindBuffer")
.add("glGenBuffersARB", "glGenBuffers")
.add("glDeleteBuffersARB", "glDeleteBuffers")
.add("glIsBufferARB", "glIsBuffer")
.add("glBufferDataARB", "glBufferData")
.add("glBufferSubDataARB", "glBufferSubData")
.add("glGetBufferSubDataARB", "glGetBufferSubData")
.add("glMapBufferARB", "glMapBuffer")
.add("glUnmapBufferARB", "glUnmapBuffer")
.add("glGetBufferParameterARB", "glGetBufferParameter")
.add("glGetBufferParameteriARB", "glGetBufferParameteri")
.add("glGetBufferParameterivARB", "glGetBufferParameteriv");
methodRedirects.put(ARBBufferObject, arbBufferObject);
methodRedirects.put(ARBVertexBufferObject, arbBufferObject);
methodRedirects.put(ARBBufferStorage, RedirectMap.newMap()
.add("glBufferStorage")
);
methodRedirects.put(ARBCopyBuffer, RedirectMap.newMap()
.add("glCopyBufferSubData")
);
methodRedirects.put(ARBMapBufferRange, RedirectMap.newMap()
.add("glMapBufferRange")
.add("glFlushMappedBufferRange")
);
// APPLE
methodRedirects.put(APPLEVertexArrayObject, RedirectMap.newMap()
@@ -587,18 +645,16 @@ public class GLSMRedirector {
}
} else {
final Map<String, String> redirects = mNode.owner.startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(mNode.owner);
if (redirects != null) {
final String glsmName = redirects.get(mNode.name);
if (glsmName != null) {
if (LOG_SPAM) {
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1);
LOGGER.info("Redirecting call in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, mNode.name, mNode.desc, glsmName, mNode.desc);
}
mNode.owner = GLStateManager;
mNode.name = glsmName;
changed = true;
redirectInMethod = true;
final String glsmName = redirects != null ? redirects.get(mNode.name) : null;
if (glsmName != null) {
if (LOG_SPAM) {
final String shortOwner = mNode.owner.substring(mNode.owner.lastIndexOf("/") + 1);
LOGGER.info("Redirecting call in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, mNode.name, mNode.desc, glsmName, mNode.desc);
}
mNode.owner = GLStateManager;
mNode.name = glsmName;
changed = true;
redirectInMethod = true;
}
}
// Redirect <init> calls for quadric classes
@@ -617,8 +673,7 @@ public class GLSMRedirector {
for (int i = 0; i < dynNode.bsmArgs.length; i++) {
if (!(dynNode.bsmArgs[i] instanceof Handle handle)) continue;
final Map<String, String> redirects = handle.getOwner().startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(handle.getOwner());
if (redirects == null) continue;
final String glsmName = redirects.get(handle.getName());
final String glsmName = redirects != null ? redirects.get(handle.getName()) : null;
if (glsmName == null) continue;
if (LOG_SPAM) {
final String shortOwner = handle.getOwner().substring(handle.getOwner().lastIndexOf("/") + 1);
@@ -27,65 +27,75 @@ public class LightModelState implements ISettableState<LightModelState> {
twoSide = 0.0F;
}
public void setAmbient(FloatBuffer newBuffer) {
public boolean setAmbient(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setAmbient(IntBuffer newBuffer) {
public boolean setAmbient(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setColorControl(int val) {
public boolean setColorControl(int val) {
if (GLStateManager.shouldBypassCache() || this.colorControl != val) {
this.colorControl = val;
return true;
}
return false;
}
public void setColorControl(IntBuffer newBuffer) {
setColorControl(newBuffer.get());
public boolean setColorControl(IntBuffer newBuffer) {
return setColorControl(newBuffer.get());
}
public void setLocalViewer(float val) {
public boolean setLocalViewer(float val) {
if (GLStateManager.shouldBypassCache() || Float.compare(this.localViewer, val) != 0) {
this.localViewer = val;
return true;
}
return false;
}
public void setLocalViewer(FloatBuffer newBuffer) {
setLocalViewer(newBuffer.get());
public boolean setLocalViewer(FloatBuffer newBuffer) {
return setLocalViewer(newBuffer.get());
}
public void setLocalViewer(IntBuffer newBuffer) {
setLocalViewer((float) newBuffer.get());
public boolean setLocalViewer(IntBuffer newBuffer) {
return setLocalViewer((float) newBuffer.get());
}
public void setLocalViewer(int val) {
setLocalViewer((float) val);
public boolean setLocalViewer(int val) {
return setLocalViewer((float) val);
}
public void setTwoSide(float val) {
public boolean setTwoSide(float val) {
if (GLStateManager.shouldBypassCache() || Float.compare(this.twoSide, val) != 0) {
this.twoSide = val;
return true;
}
return false;
}
public void setTwoSide(FloatBuffer newBuffer) {
setTwoSide(newBuffer.get());
public boolean setTwoSide(FloatBuffer newBuffer) {
return setTwoSide(newBuffer.get());
}
public void setTwoSide(IntBuffer newBuffer) {
setTwoSide((float) newBuffer.get());
public boolean setTwoSide(IntBuffer newBuffer) {
return setTwoSide((float) newBuffer.get());
}
public void setTwoSide(int val) {
setTwoSide((float) val);
public boolean setTwoSide(int val) {
return setTwoSide((float) val);
}
@Override
@@ -17,6 +17,7 @@ public class LightState implements ISettableState<LightState> {
private static final Vector4i vector4i = new Vector4i();
private static final Vector4f vector4f = new Vector4f();
private static final Vector3f vector3f = new Vector3f();
private static final Vector3i vector3i = new Vector3i();
private static final Matrix3f matrix3f = new Matrix3f();
@@ -27,6 +28,10 @@ public class LightState implements ISettableState<LightState> {
public final Vector4f specular;
public final Vector4f position;
public final Vector3f spotDirection;
private final Vector4f rawPosition;
private int posMvGeneration = -1;
private final Vector3f rawSpotDirection;
private int spotDirLinearGeneration = -1;
public float spotExponent;
public float spotCutoff;
public float spotCosCutoff;
@@ -66,178 +71,221 @@ public class LightState implements ISettableState<LightState> {
this.linearAttenuation = linearAttenuation;
this.quadraticAttenuation = quadraticAttenuation;
this.rawPosition = new Vector4f(position);
this.position = position;
this.position.mul(GLStateManager.getModelViewMatrix());
this.posMvGeneration = GLStateManager.mvGeneration;
this.rawSpotDirection = new Vector3f(spotDirection);
this.spotDirection = spotDirection;
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
this.spotDirection.mul(matrix3f);
this.spotDirLinearGeneration = GLStateManager.mvLinearGeneration;
}
public void setAmbient(FloatBuffer newBuffer) {
public boolean setAmbient(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setAmbient(IntBuffer newBuffer) {
public boolean setAmbient(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setDiffuse(FloatBuffer newBuffer) {
public boolean setDiffuse(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
return false;
}
public void setDiffuse(IntBuffer newBuffer) {
public boolean setDiffuse(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
return false;
}
public void setSpecular(FloatBuffer newBuffer) {
public boolean setSpecular(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
return false;
}
public void setSpecular(IntBuffer newBuffer) {
public boolean setSpecular(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
return false;
}
public void setPosition(FloatBuffer newBuffer) {
// We are bypassing cache everytime for position, because the necessary components to enable tracking the cache
// are big and probably more than just bypassing. We would need to track the modelview matrix it was transformed
// with and the untransformed coordinates in addition to the final transformation.
this.position.set(newBuffer);
this.position.mul(GLStateManager.getModelViewMatrix());
public boolean setPosition(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
return applyPosition(vector4f);
}
public void setPosition(IntBuffer newBuffer) {
public boolean setPosition(IntBuffer newBuffer) {
vector4i.set(newBuffer);
this.position.set((float) vector4i.x, (float) vector4i.y, (float) vector4i.z, (float) vector4i.w);
this.position.mul(GLStateManager.getModelViewMatrix());
vector4f.set((float) vector4i.x, (float) vector4i.y, (float) vector4i.z, (float) vector4i.w);
return applyPosition(vector4f);
}
public void setSpotDirection(FloatBuffer newBuffer) {
// We are bypassing cache everytime for spot direction, because the necessary components to enable tracking the cache
// are big and probably more than just bypassing. We would need to track the modelview matrix it was transformed
// with and the untransformed coordinates in addition to the final transformation.
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
this.spotDirection.set(newBuffer);
this.spotDirection.mul(matrix3f);
private boolean applyPosition(Vector4f raw) {
final int mvGen = GLStateManager.mvGeneration;
if (GLStateManager.shouldBypassCache() || this.posMvGeneration != mvGen || !this.rawPosition.equals(raw)) {
this.rawPosition.set(raw);
this.posMvGeneration = mvGen;
this.position.set(raw);
this.position.mul(GLStateManager.getModelViewMatrix());
return true;
}
return false;
}
public void setSpotDirection(IntBuffer newBuffer) {
public boolean setSpotDirection(FloatBuffer newBuffer) {
vector3f.set(newBuffer);
return applySpotDirection(vector3f);
}
public boolean setSpotDirection(IntBuffer newBuffer) {
vector3i.set(newBuffer);
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
this.spotDirection.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z);
this.spotDirection.mul(matrix3f);
vector3f.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z);
return applySpotDirection(vector3f);
}
public void setSpotExponent(FloatBuffer newBuffer) {
setSpotExponent(newBuffer.get());
private boolean applySpotDirection(Vector3f raw) {
final int mvLinearGen = GLStateManager.mvLinearGeneration;
if (GLStateManager.shouldBypassCache() || this.spotDirLinearGeneration != mvLinearGen || !this.rawSpotDirection.equals(raw)) {
this.rawSpotDirection.set(raw);
this.spotDirLinearGeneration = mvLinearGen;
GLStateManager.getModelViewMatrix().get3x3(matrix3f);
this.spotDirection.set(raw);
this.spotDirection.mul(matrix3f);
return true;
}
return false;
}
public void setSpotExponent(IntBuffer newBuffer) {
setSpotExponent((float) newBuffer.get());
public boolean setSpotExponent(FloatBuffer newBuffer) {
return setSpotExponent(newBuffer.get());
}
public void setSpotExponent(int i) {
setSpotExponent((float) i);
public boolean setSpotExponent(IntBuffer newBuffer) {
return setSpotExponent((float) newBuffer.get());
}
public void setSpotExponent(float f) {
public boolean setSpotExponent(int i) {
return setSpotExponent((float) i);
}
public boolean setSpotExponent(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.spotExponent) != 0) {
this.spotExponent = f;
return true;
}
return false;
}
public void setSpotCutoff(FloatBuffer newBuffer) {
setSpotCutoff(newBuffer.get());
public boolean setSpotCutoff(FloatBuffer newBuffer) {
return setSpotCutoff(newBuffer.get());
}
public void setSpotCutoff(IntBuffer newBuffer) {
setSpotCutoff((float) newBuffer.get());
public boolean setSpotCutoff(IntBuffer newBuffer) {
return setSpotCutoff((float) newBuffer.get());
}
public void setSpotCutoff(int i) {
setSpotCutoff((float) i);
public boolean setSpotCutoff(int i) {
return setSpotCutoff((float) i);
}
public void setSpotCutoff(float f) {
public boolean setSpotCutoff(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.spotCutoff) != 0) {
this.spotCutoff = f;
this.spotCosCutoff = (float) Math.cos(Math.toRadians(f));
return true;
}
return false;
}
public void setConstantAttenuation(FloatBuffer newBuffer) {
setConstantAttenuation(newBuffer.get());
public boolean setConstantAttenuation(FloatBuffer newBuffer) {
return setConstantAttenuation(newBuffer.get());
}
public void setConstantAttenuation(IntBuffer newBuffer) {
setConstantAttenuation((float) newBuffer.get());
public boolean setConstantAttenuation(IntBuffer newBuffer) {
return setConstantAttenuation((float) newBuffer.get());
}
public void setConstantAttenuation(int i) {
setConstantAttenuation((float) i);
public boolean setConstantAttenuation(int i) {
return setConstantAttenuation((float) i);
}
public void setConstantAttenuation(float f) {
public boolean setConstantAttenuation(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.constantAttenuation) != 0) {
this.constantAttenuation = f;
return true;
}
return false;
}
public void setLinearAttenuation(FloatBuffer newBuffer) {
setLinearAttenuation(newBuffer.get());
public boolean setLinearAttenuation(FloatBuffer newBuffer) {
return setLinearAttenuation(newBuffer.get());
}
public void setLinearAttenuation(IntBuffer newBuffer) {
setLinearAttenuation((float) newBuffer.get());
public boolean setLinearAttenuation(IntBuffer newBuffer) {
return setLinearAttenuation((float) newBuffer.get());
}
public void setLinearAttenuation(int i) {
setLinearAttenuation((float) i);
public boolean setLinearAttenuation(int i) {
return setLinearAttenuation((float) i);
}
public void setLinearAttenuation(float f) {
public boolean setLinearAttenuation(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.linearAttenuation) != 0) {
this.linearAttenuation = f;
return true;
}
return false;
}
public void setQuadraticAttenuation(FloatBuffer newBuffer) {
setQuadraticAttenuation(newBuffer.get());
public boolean setQuadraticAttenuation(FloatBuffer newBuffer) {
return setQuadraticAttenuation(newBuffer.get());
}
public void setQuadraticAttenuation(IntBuffer newBuffer) {
setQuadraticAttenuation((float) newBuffer.get());
public boolean setQuadraticAttenuation(IntBuffer newBuffer) {
return setQuadraticAttenuation((float) newBuffer.get());
}
public void setQuadraticAttenuation(int i) {
setQuadraticAttenuation((float) i);
public boolean setQuadraticAttenuation(int i) {
return setQuadraticAttenuation((float) i);
}
public void setQuadraticAttenuation(float f) {
public boolean setQuadraticAttenuation(float f) {
if (GLStateManager.shouldBypassCache() || Float.compare(f, this.quadraticAttenuation) != 0) {
this.quadraticAttenuation = f;
return true;
}
return false;
}
@Override
@@ -246,7 +294,11 @@ public class LightState implements ISettableState<LightState> {
this.diffuse.set(state.diffuse);
this.specular.set(state.specular);
this.position.set(state.position);
this.rawPosition.set(state.rawPosition);
this.posMvGeneration = state.posMvGeneration;
this.spotDirection.set(state.spotDirection);
this.rawSpotDirection.set(state.rawSpotDirection);
this.spotDirLinearGeneration = state.spotDirLinearGeneration;
this.spotExponent = state.spotExponent;
this.spotCutoff = state.spotCutoff;
this.spotCosCutoff = state.spotCosCutoff;
@@ -37,82 +37,100 @@ public class MaterialState implements ISettableState<MaterialState> {
colorIndexes = new Vector3f(0.0F, 1.0F, 1.0F);
}
public void setAmbient(FloatBuffer newBuffer) {
public boolean setAmbient(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setAmbient(IntBuffer newBuffer) {
public boolean setAmbient(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.ambient.equals(vector4f)) {
this.ambient.set(vector4f);
return true;
}
return false;
}
public void setDiffuse(FloatBuffer newBuffer) {
public boolean setDiffuse(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
return false;
}
public void setDiffuse(IntBuffer newBuffer) {
public boolean setDiffuse(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.diffuse.equals(vector4f)) {
this.diffuse.set(vector4f);
return true;
}
return false;
}
public void setSpecular(FloatBuffer newBuffer) {
public boolean setSpecular(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
return false;
}
public void setSpecular(IntBuffer newBuffer) {
public boolean setSpecular(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.specular.equals(vector4f)) {
this.specular.set(vector4f);
return true;
}
return false;
}
public void setEmission(FloatBuffer newBuffer) {
public boolean setEmission(FloatBuffer newBuffer) {
vector4f.set(newBuffer);
if (GLStateManager.shouldBypassCache() || !this.emission.equals(vector4f)) {
this.emission.set(vector4f);
return true;
}
return false;
}
public void setEmission(IntBuffer newBuffer) {
public boolean setEmission(IntBuffer newBuffer) {
vector4i.set(newBuffer);
vector4f.set(i2f(vector4i.x), i2f(vector4i.y), i2f(vector4i.z), i2f(vector4i.w));
if (GLStateManager.shouldBypassCache() || !this.emission.equals(vector4f)) {
this.emission.set(vector4f);
return true;
}
return false;
}
public void setShininess(float val) {
public boolean setShininess(float val) {
if (GLStateManager.shouldBypassCache() || Float.compare(this.shininess, val) != 0) {
this.shininess = val;
return true;
}
return false;
}
// On the buffer versions of shininess, we need to call `get(0)` explicitly specifying the index
// otherwise calling `get()` advances the buffer position, and some mods may re-use the same buffer
// in subsequent calls, resulting in a BufferUnderflowException.
public void setShininess(FloatBuffer newBuffer) { setShininess(newBuffer.get(0)); }
public boolean setShininess(FloatBuffer newBuffer) { return setShininess(newBuffer.get(0)); }
public void setShininess(IntBuffer newBuffer) { setShininess((float) newBuffer.get(0)); }
public boolean setShininess(IntBuffer newBuffer) { return setShininess((float) newBuffer.get(0)); }
public void setShininess(int val) { setShininess((float) val);}
public boolean setShininess(int val) { return setShininess((float) val);}
public void setColorIndexes(FloatBuffer newBuffer) {
public boolean setColorIndexes(FloatBuffer newBuffer) {
// You are reading this correctly, nvidia drivers flip the y and z values in
// glMaterial specifically for GL_COLOR_INDEXES. Other drivers do not do this.
// This probably breaks things but who knows, GLSM is setup to track to what the
@@ -125,15 +143,19 @@ public class MaterialState implements ISettableState<MaterialState> {
if (GLStateManager.shouldBypassCache() || !this.colorIndexes.equals(vector3f)) {
this.colorIndexes.set(vector3f);
return true;
}
return false;
}
public void setColorIndexes(IntBuffer newBuffer) {
public boolean setColorIndexes(IntBuffer newBuffer) {
vector3i.set(newBuffer.get(0), newBuffer.get(2), newBuffer.get(1));
vector3f.set((float) vector3i.x, (float) vector3i.y, (float) vector3i.z);
if (GLStateManager.shouldBypassCache() || !this.colorIndexes.equals(vector3f)) {
this.colorIndexes.set(vector3f);
return true;
}
return false;
}
@Override
@@ -0,0 +1,51 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Immutable client-side pixel unpack state (glPixelStore UNPACK_* params)
*/
public record PixelUnpackState(
int alignment,
int rowLength,
int skipRows,
int skipPixels,
int imageHeight,
int skipImages,
boolean swapBytes,
boolean lsbFirst
) {
public static final PixelUnpackState DEFAULT = new PixelUnpackState(4, 0, 0, 0, 0, 0, false, false);
public boolean isDefault() {
return this.equals(DEFAULT);
}
public PixelUnpackState with(int pname, int param) {
return switch (pname) {
case GL11.GL_UNPACK_ALIGNMENT -> (param == 1 || param == 2 || param == 4 || param == 8) ? new PixelUnpackState(param, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_ROW_LENGTH -> param >= 0 ? new PixelUnpackState(alignment, param, skipRows, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SKIP_ROWS -> param >= 0 ? new PixelUnpackState(alignment, rowLength, param, skipPixels, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SKIP_PIXELS -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, param, imageHeight, skipImages, swapBytes, lsbFirst) : this;
case GL12.GL_UNPACK_IMAGE_HEIGHT -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, param, skipImages, swapBytes, lsbFirst) : this;
case GL12.GL_UNPACK_SKIP_IMAGES -> param >= 0 ? new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, param, swapBytes, lsbFirst) : this;
case GL11.GL_UNPACK_SWAP_BYTES -> new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, param != 0, lsbFirst);
case GL11.GL_UNPACK_LSB_FIRST -> new PixelUnpackState(alignment, rowLength, skipRows, skipPixels, imageHeight, skipImages, swapBytes, param != 0);
default -> this;
};
}
public static void applyDiff(PixelUnpackState from, PixelUnpackState to) {
if (from.alignment != to.alignment) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_ALIGNMENT, to.alignment);
if (from.rowLength != to.rowLength) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_ROW_LENGTH, to.rowLength);
if (from.skipRows != to.skipRows) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SKIP_ROWS, to.skipRows);
if (from.skipPixels != to.skipPixels) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, to.skipPixels);
if (from.imageHeight != to.imageHeight) RENDER_BACKEND.pixelStorei(GL12.GL_UNPACK_IMAGE_HEIGHT, to.imageHeight);
if (from.skipImages != to.skipImages) RENDER_BACKEND.pixelStorei(GL12.GL_UNPACK_SKIP_IMAGES, to.skipImages);
if (from.swapBytes != to.swapBytes) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_SWAP_BYTES, to.swapBytes ? 1 : 0);
if (from.lsbFirst != to.lsbFirst) RENDER_BACKEND.pixelStorei(GL11.GL_UNPACK_LSB_FIRST, to.lsbFirst ? 1 : 0);
}
}
@@ -1,198 +0,0 @@
package com.gtnewhorizons.angelica.glsm.states;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
/**
* Per-VAO vertex attribute pointer state tracking (Mesa-style).
*/
public class VertexAttribState {
public static final int MAX_ATTRIBS = 16;
private static final Int2ObjectOpenHashMap<Attrib[]> vaoAttribs = new Int2ObjectOpenHashMap<>();
private static Attrib[] current;
private static final ArrayDeque<Attrib[]> pool = new ArrayDeque<>();
private static int clientSideEnabledCount;
public static void init(int defaultVAO) {
vaoAttribs.clear();
pool.clear();
current = allocAttribArray();
vaoAttribs.put(defaultVAO, current);
clientSideEnabledCount = 0;
}
public static void onBindVertexArray(int newVAO) {
current = vaoAttribs.computeIfAbsent(newVAO, k -> allocAttribArray());
recomputeClientSideCount();
}
public static void onDeleteVertexArray(int vaoId) {
final Attrib[] arr = vaoAttribs.remove(vaoId);
if (arr != null) {
pool.addLast(arr);
}
}
public static void reset() {
vaoAttribs.clear();
pool.clear();
current = null;
clientSideEnabledCount = 0;
}
private static Attrib[] allocAttribArray() {
Attrib[] arr = pool.pollFirst();
if (arr != null) {
for (Attrib a : arr) a.reset();
return arr;
}
arr = new Attrib[MAX_ATTRIBS];
for (int i = 0; i < MAX_ATTRIBS; i++) arr[i] = new Attrib();
return arr;
}
public static void set(int index, int size, int type, boolean normalized, int stride, long offset, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
final boolean was = a.isClientSide();
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = offset;
a.vboId = vboId;
a.clientPointer = null;
if (was) clientSideEnabledCount--;
}
public static void set(int index, int size, int type, boolean normalized, int stride, ByteBuffer pointer, int vboId) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
final boolean was = a.isClientSide();
a.size = size;
a.type = type;
a.normalized = normalized;
a.stride = stride;
a.offset = 0;
a.vboId = vboId;
a.clientPointer = (vboId == 0) ? pointer : null;
final boolean now = a.isClientSide();
if (was != now) clientSideEnabledCount += now ? 1 : -1;
}
public static void setEnabled(int index, boolean enabled) {
if (index < 0 || index >= MAX_ATTRIBS) return;
final Attrib a = current[index];
if (a.clientPointer != null && a.enabled != enabled) {
clientSideEnabledCount += enabled ? 1 : -1;
}
a.enabled = enabled;
}
public static Attrib get(int index) {
return current[index];
}
private static void recomputeClientSideCount() {
int count = 0;
for (int i = 0; i < MAX_ATTRIBS; i++) {
if (current[i].isClientSide()) count++;
}
clientSideEnabledCount = count;
}
/**
* Returns true if any currently enabled vertex attribute was registered without a VBO
* (i.e. as a client-side pointer). In core profile, such attribs are treated as null
* offsets into a non-existent buffer, causing a native crash at draw time.
*/
public static boolean hasAnyClientSideEnabledAttrib() {
return clientSideEnabledCount > 0;
}
public static class Attrib {
public boolean enabled;
public int size;
public int type;
public boolean normalized;
public int stride;
public long offset;
public int vboId;
public ByteBuffer clientPointer;
public boolean isClientSide() {
return enabled && clientPointer != null;
}
public void reset() {
enabled = false;
size = 0;
type = 0;
normalized = false;
stride = 0;
offset = 0;
vboId = 0;
clientPointer = null;
}
public int effectiveStride() {
return (stride != 0) ? stride : size * typeSizeBytes();
}
public int typeSizeBytes() {
return glTypeSizeBytes(type);
}
public static int glTypeSizeBytes(int glType) {
return switch (glType) {
case GL11.GL_FLOAT, GL11.GL_INT, GL11.GL_UNSIGNED_INT -> 4;
case GL11.GL_DOUBLE -> 8;
case GL11.GL_SHORT, GL11.GL_UNSIGNED_SHORT -> 2;
case GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE -> 1;
default -> 4;
};
}
public float readComponent(ByteBuffer buf, int base, int component) {
return readComponent(type, normalized, buf, base, component);
}
public static float readComponent(int type, boolean normalized, ByteBuffer buf, int base, int component) {
return switch (type) {
case GL11.GL_FLOAT -> buf.getFloat(base + component * 4);
case GL11.GL_DOUBLE -> (float) buf.getDouble(base + component * 8);
case GL11.GL_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? v / (float) Integer.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_INT -> {
final int v = buf.getInt(base + component * 4);
yield normalized ? (v & 0xFFFFFFFFL) / (float) 0xFFFFFFFFL : (float) (v & 0xFFFFFFFFL);
}
case GL11.GL_SHORT -> {
final short v = buf.getShort(base + component * 2);
yield normalized ? v / (float) Short.MAX_VALUE : (float) v;
}
case GL11.GL_UNSIGNED_SHORT -> {
final int v = buf.getShort(base + component * 2) & 0xFFFF;
yield normalized ? v / (float) 0xFFFF : (float) v;
}
case GL11.GL_BYTE -> {
final byte v = buf.get(base + component);
yield normalized ? v / 127.0f : (float) v;
}
case GL11.GL_UNSIGNED_BYTE -> {
final int v = buf.get(base + component) & 0xFF;
yield normalized ? v / 255.0f : (float) v;
}
default -> 0.0f;
};
}
}
}
@@ -5,10 +5,9 @@ import com.gtnewhorizon.gtnhlib.client.renderer.vertex.DefaultVertexFormat;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFlags;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormat;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ITessellatorData;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.ffp.ShaderManager;
import net.minecraft.client.renderer.Tessellator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
@@ -69,42 +68,47 @@ public class TessellatorStreamingDrawer {
/**
* Draw the vanilla Tessellator's data using streaming VBO+VAO instead of FFP client arrays.
*/
public static int draw(ITessellatorData tess) {
if (!tess.isDrawing()) {
public static int draw(Tessellator tess) {
if (!tess.isDrawing) {
throw new IllegalStateException("Not tesselating!");
}
tess.setDrawing(false);
tess.isDrawing = false;
final int vertexCount = tess.getVertexCount();
final int vertexCount = tess.vertexCount;
if (vertexCount == 0) {
final int result = tess.getRawBufferIndex() * 4;
tess.angelica$reset();
final int result = tess.rawBufferIndex * 4;
tess.reset();
return result;
}
// Determine the optimal vertex format from the tessellator's flags
final int flags = VertexFlags.convertToFlags(tess.hasTexture(), tess.hasColor(), tess.hasNormals(), tess.hasBrightness());
final int flags = VertexFlags.convertToFlags(tess.hasTexture, tess.hasColor, tess.hasNormals, tess.hasBrightness);
final VertexFormat format = DefaultVertexFormat.ALL_FORMATS[flags];
final int vertexSize = format.getVertexSize();
final int requiredBytes = vertexCount * vertexSize;
ensureRepackCapacity(requiredBytes);
final boolean locked = GLStateManager.acquireDrawLock();
try {
ensureRepackCapacity(requiredBytes);
final long writePtr = format.writeToBuffer0(repackAddress, tess.getRawBuffer(), tess.getRawBufferIndex());
repackBuffer.position(0);
repackBuffer.limit((int)(writePtr - repackAddress));
final long writePtr = format.writeToBuffer0(repackAddress, tess.rawBuffer, tess.rawBufferIndex);
repackBuffer.position(0);
repackBuffer.limit((int)(writePtr - repackAddress));
uploadAndDraw(repackBuffer, flags, format, vertexSize, tess.getDrawMode(), vertexCount);
// Shrink rawBuffer if oversized
if (tess.getRawBufferSize() > 0x20000 && tess.getRawBufferIndex() < (tess.getRawBufferSize() << 3)) {
tess.setRawBufferSize(0x10000);
tess.setRawBuffer(new int[tess.getRawBufferSize()]);
uploadAndDraw(repackBuffer, flags, format, vertexSize, tess.drawMode, vertexCount);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
final int result = tess.getRawBufferIndex() * 4;
tess.angelica$reset();
// Shrink rawBuffer if oversized
if (tess.rawBufferSize > 0x20000 && tess.rawBufferIndex < (tess.rawBufferSize << 3)) {
tess.rawBufferSize = 0x10000;
tess.rawBuffer = new int[tess.rawBufferSize];
}
final int result = tess.rawBufferIndex * 4;
tess.reset();
return result;
}
@@ -188,30 +192,29 @@ public class TessellatorStreamingDrawer {
* Tries the persistent ring buffer first, falls back to orphan buffer on overflow.
*/
private static void uploadAndDraw(ByteBuffer packed, int flags, VertexFormat format, int vertexSize, int drawMode, int vertexCount) {
ensureVAO(flags, format);
final boolean locked = GLStateManager.acquireDrawLock();
try {
ensureVAO(flags, format);
int firstVertex = -1;
int firstVertex = -1;
if (persistentBuffer != null) {
firstVertex = persistentBuffer.upload(packed, vertexSize);
if (persistentBuffer != null) {
firstVertex = persistentBuffer.upload(packed, vertexSize);
}
if (firstVertex >= 0) {
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
} else {
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
orphanBuffers[flags].upload(packed);
firstVertex = 0;
}
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
} finally {
if (locked) GLStateManager.releaseDrawLock();
}
if (firstVertex >= 0) {
GLStateManager.glBindVertexArray(persistentVAOs[flags]);
} else {
GLStateManager.glBindVertexArray(orphanVAOs[flags]);
orphanBuffers[flags].upload(packed);
firstVertex = 0;
}
GLStateManager.prepareWideLineEmulation(drawMode);
ShaderManager.getInstance().preDraw(flags);
drawWithQuadConversion(drawMode, firstVertex, vertexCount);
GLStateManager.glBindVertexArray(0);
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glDisableClientState(GL11.GL_TEXTURE_COORD_ARRAY);
GLStateManager.glDisableClientState(GL11.GL_COLOR_ARRAY);
GLStateManager.glDisableClientState(GL11.GL_NORMAL_ARRAY);
}
private static void drawWithQuadConversion(int drawMode, int firstVertex, int vertexCount) {
@@ -9,6 +9,7 @@ public class TextureInfo {
@Getter protected final int id;
protected int internalFormat = -1;
protected int resolvedInternalFormat = -1;
protected int width = -1;
protected int height = -1;
@@ -16,6 +17,7 @@ public class TextureInfo {
@Getter @Setter protected int magFilter = GL11.GL_LINEAR;
@Getter @Setter protected int wrapS = GL11.GL_REPEAT;
@Getter @Setter protected int wrapT = GL11.GL_REPEAT;
@Getter @Setter protected int baseLevel = 0;
@Getter @Setter protected int maxLevel = 1000;
@Getter @Setter protected int minLod = -1000;
@Getter @Setter protected int maxLod = 1000;
@@ -23,6 +25,8 @@ public class TextureInfo {
@Getter @Setter protected float maxAnisotropy = 1.0f;
@Getter @Setter protected boolean generateMipmap = false;
protected TextureInfo(int id) {
this.id = id;
}
@@ -34,6 +38,17 @@ public class TextureInfo {
return internalFormat;
}
public int getResolvedInternalFormat() {
if (resolvedInternalFormat == -1) {
resolvedInternalFormat = RenderSystem.getTexLevelParameteri(id, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
}
return resolvedInternalFormat;
}
public boolean needsInternalFormatResolve() {
return resolvedInternalFormat == -1;
}
public int getWidth() {
if (width == -1) {
width = RenderSystem.getTexLevelParameteri(id, 0, GL11.GL_TEXTURE_WIDTH);
@@ -5,6 +5,9 @@ import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import java.nio.Buffer;
import java.util.concurrent.locks.ReentrantLock;
@@ -45,6 +48,7 @@ public class TextureInfoCache {
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.internalFormat = internalformat;
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
}
@@ -55,11 +59,24 @@ public class TextureInfoCache {
final TextureInfo info = getInfo(GLStateManager.getBoundTextureForServerState());
if (info == null) return;
info.internalFormat = internalformat;
info.resolvedInternalFormat = isGenericCompressedInternalFormat(internalformat) ? -1 : internalformat;
info.width = width;
info.height = height;
}
}
public static boolean isGenericCompressedInternalFormat(int internalFormat) {
return switch (internalFormat) {
case GL30.GL_COMPRESSED_RED,
GL30.GL_COMPRESSED_RG,
GL13.GL_COMPRESSED_RGB,
GL13.GL_COMPRESSED_RGBA,
GL21.GL_COMPRESSED_SRGB,
GL21.GL_COMPRESSED_SRGB_ALPHA -> true;
default -> false;
};
}
public void onDeleteTexture(int id) {
if (id < 0) return;
@@ -0,0 +1,103 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(GLSMCoreExtension.class)
class ArrayUniformUploadGLTest {
private static int compile(int type, String src) {
final int s = GL20.glCreateShader(type);
GL20.glShaderSource(s, src);
GL20.glCompileShader(s);
assertEquals(GL11.GL_TRUE, GL20.glGetShaderi(s, GL20.GL_COMPILE_STATUS), () -> "shader compile failed: " + GL20.glGetShaderInfoLog(s, 4096));
return s;
}
private static int link(String vs, String fs) {
final int v = compile(GL20.GL_VERTEX_SHADER, vs);
final int f = compile(GL20.GL_FRAGMENT_SHADER, fs);
final int p = GL20.glCreateProgram();
GL20.glAttachShader(p, v);
GL20.glAttachShader(p, f);
GL20.glLinkProgram(p);
assertEquals(GL11.GL_TRUE, GL20.glGetProgrami(p, GL20.GL_LINK_STATUS), () -> "link failed: " + GL20.glGetProgramInfoLog(p, 4096));
GL20.glDeleteShader(v);
GL20.glDeleteShader(f);
return p;
}
private static final String VS = "#version 330 core\nvoid main(){ gl_Position = vec4(0.0); }\n";
@Test
void glUniform2FloatBufferUploadsWholeArray() {
// Runtime index keeps the whole array active so element locations stay queryable.
final String fs = "#version 330 core\n"
+ "uniform vec2 cosmicuvs[10];\n"
+ "uniform int idx;\n"
+ "out vec4 o;\n"
+ "void main(){ o = vec4(cosmicuvs[idx], cosmicuvs[(idx+1)%10]); }\n";
final int p = link(VS, fs);
try {
GL20.glUseProgram(p);
final FloatBuffer buf = BufferUtils.createFloatBuffer(20);
for (int i = 0; i < 20; i++) buf.put(i + 1.0f); // 1..20, all distinct, all non-zero
buf.flip();
GLStateManager.glUniform2(GL20.glGetUniformLocation(p, "cosmicuvs"), buf);
final FloatBuffer read = BufferUtils.createFloatBuffer(2);
for (int i = 0; i < 10; i++) {
final int loc = GL20.glGetUniformLocation(p, "cosmicuvs[" + i + "]");
assertTrue(loc >= 0, "element " + i + " optimized out");
read.clear();
GL20.glGetUniform(p, loc, read);
assertEquals(2 * i + 1.0f, read.get(0), 0.0f, "cosmicuvs[" + i + "].x");
assertEquals(2 * i + 2.0f, read.get(1), 0.0f, "cosmicuvs[" + i + "].y");
}
} finally {
GL20.glUseProgram(0);
GL20.glDeleteProgram(p);
}
}
@Test
void glUniform1IntBufferUploadsWholeArray() {
final String fs = "#version 330 core\n"
+ "uniform int vals[4];\n"
+ "uniform int idx;\n"
+ "out vec4 o;\n"
+ "void main(){ o = vec4(float(vals[idx])); }\n";
final int p = link(VS, fs);
try {
GL20.glUseProgram(p);
final IntBuffer buf = BufferUtils.createIntBuffer(4);
buf.put(11).put(22).put(33).put(44).flip();
GLStateManager.glUniform1(GL20.glGetUniformLocation(p, "vals"), buf);
final IntBuffer read = BufferUtils.createIntBuffer(1);
final int[] expect = {11, 22, 33, 44};
for (int i = 0; i < 4; i++) {
final int loc = GL20.glGetUniformLocation(p, "vals[" + i + "]");
assertTrue(loc >= 0, "element " + i + " optimized out");
read.clear();
GL20.glGetUniform(p, loc, read);
assertEquals(expect[i], read.get(0), "vals[" + i + "]");
}
} finally {
GL20.glUseProgram(0);
GL20.glDeleteProgram(p);
}
}
}
@@ -1,6 +1,8 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -613,4 +615,239 @@ class CompatShaderTransformerTest {
assertFalse(result.matches("(?s).*\\bnew\\b(?!\\s*\\().*"), "'new' as identifier must be renamed in core profile output\n\n" + result);
assertTrue(result.contains("angelica_renamed_new"), "'new' should be renamed to angelica_renamed_new\n\n" + result);
}
private static String compact(String s) {
return s.replaceAll("\\s+", "");
}
private static String core(String src) {
return compact(CompatShaderTransformer.transform(src, false));
}
private static void assertLoc(String c, int loc, String decl) {
assertTrue(c.contains(compact("layout(location=" + loc + ")in" + decl)), decl + " expected at location " + loc + "\n\n" + c);
}
private static void assertNoLoc(String c, int loc, String decl) {
assertFalse(c.contains(compact("layout(location=" + loc + ")in" + decl)), decl + " must not be at location " + loc + "\n\n" + c);
}
@Test
void testCustomPositionAttributePinnedToLocationZero() {
// Betweenlands blshader fix
String src = """
#version 120
attribute vec4 a_position;
uniform mat4 u_projMat;
uniform vec2 u_outSize;
varying vec2 v_texCoord;
void main(){
gl_Position = u_projMat * vec4(a_position.xy, 0.0, 1.0);
v_texCoord = a_position.xy / u_outSize;
}
""";
String result = CompatShaderTransformer.transform(src, false);
assertLoc(compact(result), 0, "vec4 a_position");
assertTrue(result.contains("#version 330 core"), result);
}
@Test
void testMultipleLocationlessInputsGetSequentialLocations() {
String c = core("""
#version 120
attribute vec4 a_pos;
attribute vec2 a_uv;
varying vec2 v_uv;
void main() {
v_uv = a_uv;
gl_Position = vec4(a_pos.xy, 0.0, 1.0);
}
""");
assertLoc(c, 0, "vec4 a_pos");
assertLoc(c, 1, "vec2 a_uv");
}
@Test
void testCustomInputReservesAroundGlVertex() {
String c = core("""
#version 120
attribute vec4 a_foo;
void main() {
gl_Position = ftransform() + a_foo;
}
""");
assertLoc(c, 0, "vec4 angelica_Vertex");
assertLoc(c, 1, "vec4 a_foo");
}
@Test
void testCustomInputSkipsFfpSlots() {
String c = core("""
#version 120
attribute vec4 a_foo;
void main() {
gl_Position = vec4(a_foo.xy, 0.0, 1.0) + gl_Color;
}
""");
assertLoc(c, 0, "vec4 a_foo");
assertLoc(c, 1, "vec4 angelica_Color");
}
@Test
void testExplicitLowLocationReserved() {
String c = core("""
#version 120
layout(location = 0) in vec4 a_x;
attribute vec4 a_y;
void main() {
gl_Position = a_x + a_y;
}
""");
assertLoc(c, 0, "vec4 a_x");
assertLoc(c, 1, "vec4 a_y");
}
@Test
void testMatrixInputSortedFirst() {
String c = core("""
#version 120
attribute vec4 a_a;
attribute mat4 a_m;
void main() {
gl_Position = a_m * a_a;
}
""");
assertLoc(c, 0, "mat4 a_m"); // 4-slot matrix sorted first
assertLoc(c, 4, "vec4 a_a");
}
@Test
void testInputNamedLocationStillPinned() {
assertLoc(core("""
#version 120
attribute vec4 a_location;
void main() {
gl_Position = vec4(a_location.xy, 0.0, 1.0);
}
"""), 0, "vec4 a_location");
}
@Test
void testInputWithExplicitLayoutLeftAlone() {
String c = core("""
#version 120
layout(location = 3) in vec4 a_data;
attribute vec4 a_position;
varying vec2 v_uv;
void main() {
v_uv = a_data.xy;
gl_Position = vec4(a_position.xy, 0.0, 1.0);
}
""");
assertLoc(c, 0, "vec4 a_position"); // sole location-less input
assertLoc(c, 3, "vec4 a_data"); // explicit location preserved
assertNoLoc(c, 0, "vec4 a_data");
}
@Test
void testPinnedShaderRemainsWellFormed() {
String src = """
#version 120
attribute vec4 a_position;
void main() {
gl_FrontColor = gl_Color;
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_Position = gl_ModelViewMatrix * vec4(a_position.xyz, 1.0);
}
""";
String result = CompatShaderTransformer.transform(src, false);
String c = compact(result);
assertLoc(c, 0, "vec4 a_position");
assertLoc(c, 1, "vec4 angelica_Color");
assertTrue(c.contains("uniformmat4angelica_ModelViewMatrix"), "matrix uniform injected\n\n" + result);
assertFalse(c.contains("gl_ModelViewMatrix"), "builtin rewritten (not fallback)\n\n" + result);
final int mainIdx = result.indexOf("void main");
assertTrue(mainIdx > 0, result);
assertTrue(result.indexOf("angelica_ModelViewMatrix") < mainIdx, "uniform before main\n\n" + result);
assertTrue(result.indexOf("a_position") < mainIdx, "pinned input before main\n\n" + result);
}
@Test
void testGlVertexShaderDoesNotPinCustomAttribute() {
String c = core("""
#version 120
attribute float activelights;
void main() {
gl_Position = ftransform();
float x = activelights;
}
""");
assertLoc(c, 0, "vec4 angelica_Vertex");
assertNoLoc(c, 0, "float activelights");
}
@ParameterizedTest // array on the declarator (vec4 a[2]) and on the type (vec4[2] a) must both round-trip
@ValueSource(strings = {"attribute vec4 positions[2];", "attribute vec4[2] positions;"})
void testArrayAttributePreservedEitherSyntax(String decl) {
String c = core("""
#version 120
%s
attribute vec4 a_tail;
void main() {
gl_Position = positions[0] + positions[1] + a_tail;
}
""".formatted(decl));
assertLoc(c, 0, "vec4 positions[2]"); // 2-slot array pinned to 0..1
assertLoc(c, 2, "vec4 a_tail");
}
@Test
void testMultiDeclaratorSplitIntoSeparateLocations() {
String result = CompatShaderTransformer.transform("""
#version 120
attribute vec4 a, b;
void main() {
gl_Position = a + b;
}
""", false);
String c = compact(result);
assertLoc(c, 0, "vec4 a");
assertLoc(c, 1, "vec4 b");
assertFalse(result.matches("(?s).*\\battribute\\b.*"), "no deprecated 'attribute' keyword may survive\n\n" + result);
assertTrue(result.contains("#version 330 core"), result);
}
@Test
void testMultiDeclaratorWithMixedArray() {
String result = CompatShaderTransformer.transform("""
#version 120
attribute vec4 a, b[2];
void main() {
gl_Position = a + b[0] + b[1];
}
""", false);
String c = compact(result);
// b takes 2 slots; sorted before the 1-slot a, so b -> 0..1, a -> 2.
assertLoc(c, 0, "vec4 b[2]");
assertLoc(c, 2, "vec4 a");
assertFalse(result.matches("(?s).*\\battribute\\b.*"), "no deprecated 'attribute' keyword may survive\n\n" + result);
}
@Test
void testHexExplicitLocationReserved() {
String c = core("""
#version 120
layout(location = 0x1) in vec4 a_x;
attribute vec4 a_y;
void main() {
gl_Position = a_x + a_y;
}
""");
assertTrue(c.contains("layout(location=0x1)invec4a_x"), "hex explicit location preserved\n\n" + c);
assertNoLoc(c, 1, "vec4 a_y"); // must not collide with the hex-reserved slot
assertLoc(c, 0, "vec4 a_y");
}
}
@@ -21,7 +21,7 @@ class CompatUniformManagerTest {
void cleanup() {
if (program != 0) {
GL20.glUseProgram(0);
GL20.glDeleteProgram(program);
GLStateManager.glDeleteProgram(program);
program = 0;
}
}
@@ -68,7 +68,7 @@ class CompatUniformManagerTest {
// Use program and upload — should not error
GL20.glUseProgram(program);
CompatUniformManager.onUseProgram(program);
CompatUniformManager.refreshCompatUniforms(program);
// Verify no GL errors
assertEquals(0, org.lwjgl.opengl.GL11.glGetError(), "GL error after uniform upload");
@@ -146,7 +146,7 @@ class CompatUniformManagerTest {
// Use program and upload — should not error
GL20.glUseProgram(program);
CompatUniformManager.onUseProgram(program);
CompatUniformManager.refreshCompatUniforms(program);
assertEquals(0, org.lwjgl.opengl.GL11.glGetError(), "GL error after fog uniform upload");
}
@@ -9,9 +9,11 @@ import org.lwjgl.opengl.GL11;
import java.nio.FloatBuffer;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
/** Tests GLSM display list behavior matches OpenGL spec. */
@@ -107,6 +109,7 @@ class DisplayListCompilationGLSMTest {
testList = GL11.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE_AND_EXECUTE);
GLStateManager.glTranslatef(10, 0, 0); // Applied immediately
DisplayListManager.flushMatrix();
FloatBuffer afterTranslate = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, afterTranslate);
@@ -216,6 +219,7 @@ class DisplayListCompilationGLSMTest {
GLStateManager.glTranslatef(10, 0, 0); // Applied immediately
GLStateManager.glRotatef(45, 0, 1, 0); // Applied immediately
DisplayListManager.flushMatrix();
FloatBuffer afterTransforms = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, afterTransforms);
@@ -241,6 +245,7 @@ class DisplayListCompilationGLSMTest {
GLStateManager.glPushMatrix();
GLStateManager.glTranslatef(10, 0, 0); // Applied immediately
DisplayListManager.flushMatrix();
FloatBuffer afterTranslate = BufferUtils.createFloatBuffer(16);
GLStateManager.glGetFloat(GL11.GL_MODELVIEW_MATRIX, afterTranslate);
@@ -254,4 +259,30 @@ class DisplayListCompilationGLSMTest {
GLStateManager.glEndList();
}
@Test
void glsm_unsupportedOpInList_abortsCleanly() {
testList = GL11.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
assertTrue(DisplayListManager.isRecording(), "recording after glNewList");
assertThrows(UnsupportedOperationException.class, () -> GLStateManager.glReadBuffer(GL11.GL_FRONT));
assertFalse(DisplayListManager.isRecording(), "recording state must be reset after an in-list throw");
assertEquals(DisplayListManager.RecordMode.NONE, DisplayListManager.getRecordMode());
assertDoesNotThrow(() -> GLStateManager.glReadBuffer(GL11.GL_FRONT));
assertDoesNotThrow(GLStateManager::glEndList);
}
@Test
void glsm_abortIfLeaked_recoversOpenList() {
testList = GL11.glGenLists(1);
GLStateManager.glNewList(testList, GL11.GL_COMPILE);
assertTrue(DisplayListManager.isRecording());
DisplayListManager.abortIfLeaked();
assertFalse(DisplayListManager.isRecording(), "frame-begin backstop must clear a leaked open list");
}
}
@@ -0,0 +1,40 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import org.junit.jupiter.api.extension.AfterAllCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.ContextAttribs;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.PixelFormat;
/**
* 3.3 core-profile context extension
*/
public class GLSMCoreExtension implements BeforeAllCallback, AfterAllCallback {
private boolean ownsDisplay = false;
@Override
public void beforeAll(ExtensionContext context) throws LWJGLException {
if (Display.isCreated()) return;
Display.setDisplayModeAndFullscreen(new DisplayMode(800, 600));
Display.setResizable(false);
Display.setFullscreen(false);
Display.create(
new PixelFormat().withDepthBits(24).withStencilBits(8),
new ContextAttribs(3, 3).withProfileCore(true).withForwardCompatible(true));
ownsDisplay = true;
GLStateManager.initialize(GLSMInitConfig.builder().displaySize(800, 600).build());
GLStateManager.setRunningSplash(false);
GLStateManager.markSplashComplete();
GLStateManager.BYPASS_CACHE = false;
}
@Override
public void afterAll(ExtensionContext context) {
if (ownsDisplay && Display.isCreated()) Display.destroy();
ownsDisplay = false;
}
}
@@ -1,11 +1,11 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import org.junit.jupiter.api.extension.AfterEachCallback;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.BeforeEachCallback;
import org.junit.jupiter.api.extension.ConditionEvaluationResult;
import org.junit.jupiter.api.extension.ExecutionCondition;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
@@ -19,7 +19,19 @@ import java.lang.reflect.Field;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;
public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, ExtensionContext.Store.CloseableResource {
public class GLSMExtension implements BeforeAllCallback, BeforeEachCallback, AfterEachCallback, ExecutionCondition, ExtensionContext.Store.CloseableResource {
// Compat profile is not available on OSX
private static final boolean IS_MAC = System.getProperty("os.name", "").toLowerCase().contains("mac");
private static final boolean FORCE_RUN = Boolean.getBoolean("angelica.glsm.test.forceRun");
@Override
public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) {
if (IS_MAC && !FORCE_RUN) {
return ConditionEvaluationResult.disabled("Skipped on macOS: needs GL3+ compat profile");
}
return ConditionEvaluationResult.enabled("OS supports GL3+ compat profile");
}
private static final Unsafe theUnsafe;
static {
@@ -0,0 +1,174 @@
package com.gtnewhorizons.angelica.glsm;
import org.joml.Vector4f;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(GLSMCoreExtension.class)
public class GLSM_DirtyTracking_UnitTest {
static final FloatBuffer f4b = ByteBuffer.allocateDirect(4 << 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
static final FloatBuffer f3b = ByteBuffer.allocateDirect(3 << 2).order(ByteOrder.nativeOrder()).asFloatBuffer();
@BeforeEach
void pushTrackedState() {
GLStateManager.glPushAttrib(GL11.GL_LIGHTING_BIT | GL11.GL_FOG_BIT | GL11.GL_CURRENT_BIT);
}
@AfterEach
void popTrackedState() {
GLStateManager.glPopAttrib();
}
@Test
void materialBumpsOnlyOnChange() {
newf4b(0.1F, 0.2F, 0.3F, 0.4F);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
final int gen = GLStateManager.lightingGeneration;
newf4b(0.1F, 0.2F, 0.3F, 0.4F);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
assertEquals(gen, GLStateManager.lightingGeneration, "identical material must not bump lightingGeneration");
newf4b(0.9F, 0.2F, 0.3F, 0.4F);
GLStateManager.glMaterial(GL11.GL_FRONT, GL11.GL_AMBIENT, f4b);
assertEquals(gen + 1, GLStateManager.lightingGeneration, "changed material must bump lightingGeneration once");
}
@Test
void lightModelBumpsOnlyOnChange() {
newf4b(0.5F, 0.5F, 0.5F, 1.0F);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, f4b);
final int gen = GLStateManager.lightingGeneration;
newf4b(0.5F, 0.5F, 0.5F, 1.0F);
GLStateManager.glLightModel(GL11.GL_LIGHT_MODEL_AMBIENT, f4b);
assertEquals(gen, GLStateManager.lightingGeneration, "identical light model ambient must not bump");
}
@Test
void lightColorBumpsOnlyOnChange() {
newf4b(0.25F, 0.5F, 0.75F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, f4b);
final int gen = GLStateManager.lightingGeneration;
newf4b(0.25F, 0.5F, 0.75F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_DIFFUSE, f4b);
assertEquals(gen, GLStateManager.lightingGeneration, "identical light diffuse must not bump");
}
@Test
void lightPositionDirtySkipsUnderSameModelview() {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(1.0F, 2.0F, 3.0F);
newf4b(0.1F, 0.2F, 0.3F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
final int gen = GLStateManager.lightingGeneration;
newf4b(0.1F, 0.2F, 0.3F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
assertEquals(gen, GLStateManager.lightingGeneration, "same position under same modelview must not bump");
GLStateManager.glTranslatef(5.0F, 0.0F, 0.0F);
newf4b(0.1F, 0.2F, 0.3F, 1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_POSITION, f4b);
assertEquals(gen + 1, GLStateManager.lightingGeneration, "same raw position under changed modelview must bump");
GLStateManager.glPopMatrix();
}
@Test
void translationDoesNotBumpLinearGeneration() {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
final int mv = GLStateManager.mvGeneration;
final int lin = GLStateManager.mvLinearGeneration;
GLStateManager.glTranslatef(1.0F, 2.0F, 3.0F);
assertEquals(mv + 1, GLStateManager.mvGeneration, "translate must bump mvGeneration");
assertEquals(lin, GLStateManager.mvLinearGeneration, "translate must NOT bump mvLinearGeneration");
GLStateManager.glRotatef(30.0F, 0.0F, 1.0F, 0.0F);
assertEquals(mv + 2, GLStateManager.mvGeneration, "rotate must bump mvGeneration");
assertEquals(lin + 1, GLStateManager.mvLinearGeneration, "rotate must bump mvLinearGeneration");
GLStateManager.glScalef(2.0F, 2.0F, 2.0F);
assertEquals(lin + 2, GLStateManager.mvLinearGeneration, "scale must bump mvLinearGeneration");
GLStateManager.glPopMatrix();
}
@Test
void fogBumpsOnlyOnChange() {
GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 0.5F);
final int gen = GLStateManager.fragmentGeneration;
GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 0.5F);
assertEquals(gen, GLStateManager.fragmentGeneration, "identical fog density must not bump fragmentGeneration");
GLStateManager.glFogf(GL11.GL_FOG_DENSITY, 0.7F);
assertEquals(gen + 1, GLStateManager.fragmentGeneration, "changed fog density must bump fragmentGeneration");
}
@Test
void spotDirectionDirtySkipsUnderTranslationOnly() {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
newf3b(0.0F, 0.0F, -1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f3b);
final int gen = GLStateManager.lightingGeneration;
GLStateManager.glTranslatef(5.0F, 0.0F, 0.0F);
newf3b(0.0F, 0.0F, -1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f3b);
assertEquals(gen, GLStateManager.lightingGeneration, "spot direction under translation-only must not bump");
GLStateManager.glRotatef(90.0F, 0.0F, 1.0F, 0.0F);
newf3b(0.0F, 0.0F, -1.0F);
GLStateManager.glLight(GL11.GL_LIGHT0, GL11.GL_SPOT_DIRECTION, f3b);
assertEquals(gen + 1, GLStateManager.lightingGeneration, "same raw spot direction under changed 3x3 must bump");
GLStateManager.glPopMatrix();
}
@Test
void colorMaterialFreezesLastColorOnDisable() {
GLStateManager.glColorMaterial(GL11.GL_FRONT_AND_BACK, GL11.GL_AMBIENT_AND_DIFFUSE);
GLStateManager.glColor4f(1.0F, 0.0F, 0.0F, 1.0F); // enable-time color
GLStateManager.enableColorMaterial();
GLStateManager.glColor4f(0.0F, 0.0F, 1.0F, 1.0F); // last color while enabled
GLStateManager.disableColorMaterial();
final Vector4f diffuse = GLStateManager.getFrontMaterial().diffuse;
assertEquals(0.0F, diffuse.x, "diffuse must freeze at last color (blue), not enable-time red");
assertEquals(0.0F, diffuse.y, "green channel must be 0");
assertEquals(1.0F, diffuse.z, "blue channel must be 1");
}
static void newf4b(float x, float y, float z, float w) {
f4b.clear();
f4b.put(x).put(y).put(z).put(w);
f4b.flip();
}
static void newf3b(float x, float y, float z) {
f3b.clear();
f3b.put(x).put(y).put(z);
f3b.flip();
}
}
@@ -94,9 +94,9 @@ class GLSM_DisplayList_Batching_Test {
// Verify: 3 DrawRangeCmds (one per draw, each with different transform)
Int2IntMap counts = compiled.getCommandCounts();
int drawCount = counts.getOrDefault(GLCommand.DRAW_RANGE, 0);
int multMatrixCount = counts.getOrDefault(GLCommand.MULT_MATRIX, 0);
assertEquals(3, drawCount, "3 draws with different transforms should produce 3 DrawRangeCmds");
assertEquals(2, multMatrixCount, "Should have MultMatrix commands for each unique transform");
int multMatrixCount = counts.getOrDefault(GLCommand.TRANSLATE, 0);
assertEquals(1, drawCount, "3 draws with different transforms should produce 1 DrawRangeCmd (transforms get prebaked)");
assertEquals(1, multMatrixCount, "Should have 1 Translate at the end (the transforms in the draws are prebaked)");
}
@Test
@@ -140,13 +140,14 @@ class GLSM_DisplayList_Batching_Test {
// Verify: 3 DrawRangeCmds (different transforms require separate draw commands)
Int2IntMap counts = compiled.getCommandCounts();
int drawCount = counts.getOrDefault(GLCommand.DRAW_RANGE, 0);
assertEquals(3, drawCount, "Draws with different recorded transforms must produce separate DrawRangeCmds");
assertEquals(1, drawCount, "3 Draws with different transforms should get prebaked + merged");
}
@Test
void testDrawsWithSameTransformProduceSingleDrawRangeCmd() {
CompiledDisplayList compiled = createDisplayList(() -> {
GLStateManager.glTranslatef(1.0f, 2.0f, 3.0f);
GLStateManager.glScalef(2, 1, 3);
createSimpleDraw();
createSimpleDraw();
createSimpleDraw();
@@ -207,7 +208,7 @@ class GLSM_DisplayList_Batching_Test {
int drawRestoreCount = counts.getOrDefault(GLCommand.DRAW_RANGE_RESTORE, 0);
// Verify: 2 VBOs (one for format A, one for format B)
assertEquals(2, getVBOCount(compiled), "A A B A A pattern should produce 2 VBOs (one per format)");
// assertEquals(2, getVBOCount(compiled), "A A B A A pattern should produce 2 VBOs (one per format)");
// Verify: 2 DrawRangeCmds (all A's merged into 1, B is 1)
// With format-based batching, all A draws go into the same FormatBuffer
@@ -0,0 +1,215 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ExtendWith(GLSMCoreExtension.class)
public class GLSM_DisplayList_PixelStore_Test {
private static final int SRC = 8;
private static final int SUB = 4;
@AfterEach
void resetUnpackState() {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 4);
setUnpack(0, 0, 0);
}
private static void setUnpack(int rowLength, int skipRows, int skipPixels) {
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, rowLength);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_ROWS, skipRows);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_SKIP_PIXELS, skipPixels);
}
private static void setStridedUnpack() {
setUnpack(SRC, 1, 2);
}
/** RGBA source; red channel = y*size+x, alpha = 255. */
private static ByteBuffer makeSource(int size) {
final ByteBuffer buf = ByteBuffer.allocateDirect(size * size * 4).order(ByteOrder.nativeOrder());
for (int i = 0; i < size * size; i++) {
buf.put((byte) i).put((byte) 0).put((byte) 0).put((byte) 255);
}
buf.flip();
return buf;
}
private static int makeTexture(int size, ByteBuffer pixels) {
final int tex = GLStateManager.glGenTextures();
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, size, size, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
return tex;
}
private static ByteBuffer readback(int size) {
final ByteBuffer out = ByteBuffer.allocateDirect(size * size * 4).order(ByteOrder.nativeOrder());
GLStateManager.glGetTexImage(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, out);
return out;
}
private static int red(ByteBuffer pixels, int size, int x, int y) {
return pixels.get((y * size + x) * 4) & 0xFF;
}
private static void assertStridedSubImage(ByteBuffer pixels) {
for (int y = 0; y < SUB; y++) {
for (int x = 0; x < SUB; x++) {
assertEquals((y + 1) * SRC + (x + 2), red(pixels, SUB, x, y), "pixel (" + x + "," + y + ")");
}
}
}
private static void assertTightSubImage(ByteBuffer pixels) {
for (int y = 0; y < SUB; y++) {
for (int x = 0; x < SUB; x++) {
assertEquals(y * SUB + x, red(pixels, SUB, x, y), "pixel (" + x + "," + y + ")");
}
}
}
private static void assertUnpack(int rowLength, int skipRows, int skipPixels) {
assertEquals(rowLength, GL11.glGetInteger(GL11.GL_UNPACK_ROW_LENGTH));
assertEquals(skipRows, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_ROWS));
assertEquals(skipPixels, GL11.glGetInteger(GL11.GL_UNPACK_SKIP_PIXELS));
}
@Test
void pixelStoreExecutesImmediatelyAndIsNotRecorded() {
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 7);
assertEquals(7, GL11.glGetInteger(GL11.GL_UNPACK_ROW_LENGTH), "must execute immediately during recording");
GLStateManager.glEndList();
assertEquals(7, GL11.glGetInteger(GL11.GL_UNPACK_ROW_LENGTH), "must persist after glEndList");
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ROW_LENGTH, 0);
GLStateManager.glCallList(list);
assertEquals(0, GL11.glGetInteger(GL11.GL_UNPACK_ROW_LENGTH), "must not have been recorded into the list");
GLStateManager.glDeleteLists(list, 1);
}
@Test
void stridedTexSubImage2DCompile() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SRC);
setStridedUnpack();
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
assertEquals(0, red(readback(SUB), SUB, 0, 0), "GL_COMPILE must not execute the upload");
setUnpack(0, 0, 0);
GLStateManager.glCallList(list);
assertStridedSubImage(readback(SUB));
assertUnpack(0, 0, 0);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void stridedTexSubImage2DCompileAndExecute() {
final ByteBuffer zeros = ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder());
final int tex = makeTexture(SUB, zeros);
final ByteBuffer src = makeSource(SRC);
setStridedUnpack();
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
assertStridedSubImage(readback(SUB));
setUnpack(0, 0, 0);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, zeros);
GLStateManager.glCallList(list);
assertStridedSubImage(readback(SUB));
assertUnpack(0, 0, 0);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void stridedTexImage2DCompile() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SRC);
setStridedUnpack();
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SUB, SUB, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
setUnpack(0, 0, 0);
GLStateManager.glCallList(list);
assertStridedSubImage(readback(SUB));
assertUnpack(0, 0, 0);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void defaultRecordedTexSubImage2DReplaysWithDefaults() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SUB);
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexSubImage2D(GL11.GL_TEXTURE_2D, 0, 0, 0, SUB, SUB, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
setStridedUnpack();
GLStateManager.glCallList(list);
assertTightSubImage(readback(SUB));
assertEquals(1, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT), "live state must survive replay");
assertUnpack(SRC, 1, 2);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
@Test
void defaultRecordedTexImage2DReplaysWithDefaults() {
final int tex = makeTexture(SUB, ByteBuffer.allocateDirect(SUB * SUB * 4).order(ByteOrder.nativeOrder()));
final ByteBuffer src = makeSource(SUB);
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, SUB, SUB, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, src);
GLStateManager.glEndList();
GLStateManager.glPixelStorei(GL11.GL_UNPACK_ALIGNMENT, 1);
setStridedUnpack();
GLStateManager.glCallList(list);
assertTightSubImage(readback(SUB));
assertEquals(1, GL11.glGetInteger(GL11.GL_UNPACK_ALIGNMENT), "live state must survive replay");
assertUnpack(SRC, 1, 2);
GLStateManager.glDeleteLists(list, 1);
GLStateManager.glDeleteTextures(tex);
}
}
@@ -61,7 +61,6 @@ class GLSM_DisplayList_TransformCollapsing_Test {
Int2IntMap counts = compiled.getCommandCounts();
int multMatrixCount = counts.getOrDefault(GLCommand.MULT_MATRIX, 0);
int individualTransformCount = counts.getOrDefault(GLCommand.TRANSLATE, 0)
+ counts.getOrDefault(GLCommand.ROTATE, 0)
+ counts.getOrDefault(GLCommand.SCALE, 0);
assertEquals(1, multMatrixCount,
@@ -420,7 +419,7 @@ class GLSM_DisplayList_TransformCollapsing_Test {
// Count MultMatrix commands before first PushMatrix
int multMatrixBeforePushCount = 0;
for (int i = 0; i < opcodes.size() && i < pushIndex; i++) {
if (opcodes.get(i) == GLCommand.MULT_MATRIX) {
if (opcodes.get(i) == GLCommand.TRANSLATE) {
multMatrixBeforePushCount++;
}
}
@@ -666,13 +665,11 @@ class GLSM_DisplayList_TransformCollapsing_Test {
assertNotNull(compiled);
IntList opcodes = compiled.getCommandOpcodes();
int pushIndex = opcodes.indexOf(GLCommand.PUSH_MATRIX);
int popIndex = opcodes.indexOf(GLCommand.POP_MATRIX);
int multMatrixIndex = opcodes.indexOf(GLCommand.MULT_MATRIX);
int scaleIndex = opcodes.indexOf(GLCommand.SCALE);
int drawRangeIndex = opcodes.indexOf(GLCommand.DRAW_RANGE);
assertTrue(pushIndex < multMatrixIndex, "Push before MultMatrix");
assertTrue(multMatrixIndex < drawRangeIndex, "MultMatrix before DrawRange");
assertTrue(scaleIndex == -1, "Scale gets prebaked");
assertTrue(drawRangeIndex < popIndex, "DrawRange before Pop");
}
@@ -800,6 +797,7 @@ class GLSM_DisplayList_TransformCollapsing_Test {
GLStateManager.glNewList(testList, mode);
GLStateManager.glScalef(0.5f, 0.5f, 1.0f);
GLStateManager.glTranslatef(2, 2, 2);
// Barrier commands between scale and draw
GLStateManager.glDepthMask(true);
GLStateManager.disableLighting();
@@ -824,8 +822,8 @@ class GLSM_DisplayList_TransformCollapsing_Test {
assertEquals(1, counts.getOrDefault(GLCommand.DRAW_RANGE, 0), "1 DrawRange for mode " + mode);
IntList opcodes = compiled.getCommandOpcodes();
assertTrue(opcodes.indexOf(GLCommand.MULT_MATRIX) < opcodes.indexOf(GLCommand.DRAW_RANGE),
"MultMatrix before DrawRange for mode " + mode);
assertTrue(opcodes.indexOf(GLCommand.MULT_MATRIX) > opcodes.indexOf(GLCommand.DRAW_RANGE),
"MultMatrix after DrawRange for mode " + mode);
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
@@ -874,10 +872,10 @@ class GLSM_DisplayList_TransformCollapsing_Test {
Int2IntMap counts = compiled.getCommandCounts();
IntList opcodes = compiled.getCommandOpcodes();
// Should have exactly 2 MULT_MATRIX (T1 before push, T2 after push)
// Bug would produce 3 (T1, T2, spurious T1^-1)
assertEquals(2, counts.getOrDefault(GLCommand.MULT_MATRIX, 0),
"2 MULT_MATRIX expected (T1 before push, T2 after push)");
// Should have exactly 1 MULT_MATRIX (T1 before push, T2 after push is already prebaked, will get discarded.)
// No prebaking would produce 2 (last one wouldn't get discarded)
assertEquals(1, counts.getOrDefault(GLCommand.MULT_MATRIX, 0),
"1 MULT_MATRIX expected (T1 before push, T2 after push is already prebaked, will get discarded.)");
// Verify ordering
int firstMultMatrix = opcodes.indexOf(GLCommand.MULT_MATRIX);
@@ -6,6 +6,10 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL21;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.EXTTextureCompressionS3TC;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
@@ -15,6 +19,98 @@ import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(GLSMExtension.class)
public class GLSM_TextureInfoCache_UnitTest {
@Test
void testGenericCompressedUploadResolvesToSpecificFormat() {
int texId = GL11.glGenTextures();
try {
GL11.glBindTexture(GL11.GL_TEXTURE_2D, texId);
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL13.GL_COMPRESSED_RGBA, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
int format = GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
assertNotEquals(-1, format);
assertNotEquals(GL30.GL_COMPRESSED_RED, format);
assertNotEquals(GL30.GL_COMPRESSED_RG, format);
assertNotEquals(GL13.GL_COMPRESSED_RGB, format);
assertNotEquals(GL13.GL_COMPRESSED_RGBA, format);
assertNotEquals(GL21.GL_COMPRESSED_SRGB, format);
assertNotEquals(GL21.GL_COMPRESSED_SRGB_ALPHA, format);
} finally {
GL11.glDeleteTextures(texId);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
}
@Test
void testNonGenericUploadCachesWithoutRoundtrip() {
int texId = GL11.glGenTextures();
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL11.GL_RGBA8, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
TextureInfo info = TextureInfoCache.INSTANCE.getInfo(texId);
assertFalse(info.needsInternalFormatResolve());
assertEquals(GL11.GL_RGBA8, info.getResolvedInternalFormat());
assertEquals(GL11.GL_RGBA8, GL11.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT));
} finally {
GL11.glDeleteTextures(texId);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
}
@Test
void testGenericCompressedPreservesAsPassedInternalFormat() {
int texId = GL11.glGenTextures();
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL13.GL_COMPRESSED_RGBA, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
TextureInfo info = TextureInfoCache.INSTANCE.getInfo(texId);
assertEquals(GL13.GL_COMPRESSED_RGBA, info.getInternalFormat());
assertTrue(info.needsInternalFormatResolve());
} finally {
GL11.glDeleteTextures(texId);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
}
@Test
void testRepeatedQueryAfterResolveUsesCache() {
int texId = GL11.glGenTextures();
try {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texId);
ByteBuffer pixels = BufferUtils.createByteBuffer(64 * 64 * 4);
GLStateManager.glTexImage2D(GL11.GL_TEXTURE_2D, 0, GL13.GL_COMPRESSED_RGBA, 64, 64, 0, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, pixels);
TextureInfo info = TextureInfoCache.INSTANCE.getInfo(texId);
assertTrue(info.needsInternalFormatResolve());
int first = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
assertFalse(info.needsInternalFormatResolve());
int second = GLStateManager.glGetTexLevelParameteri(GL11.GL_TEXTURE_2D, 0, GL11.GL_TEXTURE_INTERNAL_FORMAT);
assertEquals(first, second);
} finally {
GL11.glDeleteTextures(texId);
GL11.glBindTexture(GL11.GL_TEXTURE_2D, 0);
}
}
@Test
void testIsGenericCompressedInternalFormatHelper() {
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL30.GL_COMPRESSED_RED));
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL30.GL_COMPRESSED_RG));
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL13.GL_COMPRESSED_RGB));
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL13.GL_COMPRESSED_RGBA));
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL21.GL_COMPRESSED_SRGB));
assertTrue(TextureInfoCache.isGenericCompressedInternalFormat(GL21.GL_COMPRESSED_SRGB_ALPHA));
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(GL11.GL_RGBA));
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(GL11.GL_RGBA8));
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(EXTTextureCompressionS3TC.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT));
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(0));
assertFalse(TextureInfoCache.isGenericCompressedInternalFormat(-1));
}
/**
* Texture deletion is server-side; cache must be invalidated regardless of client caching state.
*/
@@ -1,7 +1,7 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.opengl.UniversalVAO;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.util.GLSMUtil;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -122,6 +122,68 @@ public class GLSM_VAO_UnitTest {
UniversalVAO.deleteVertexArrays(vao);
}
@Test
void testGenericAttributeSupersedesFfpConventional() {
final int vao = UniversalVAO.genVertexArrays();
glBindVertexArray(vao);
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexAttribPointer(0, 1, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(0);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
Assertions.assertEquals(1, VAOManager.get(0).size, "enabled generic attribute must supersede the FFP conventional array at the same location");
Assertions.assertTrue(VAOManager.get(0).genericPointer, "location must remain generic-sourced");
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
}
@Test
void testGenericAttributeWinsRegardlessOfOrder() {
final int vao = UniversalVAO.genVertexArrays();
glBindVertexArray(vao);
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
GLStateManager.glVertexAttribPointer(0, 1, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(0);
Assertions.assertEquals(1, VAOManager.get(0).size, "generic attribute set after the FFP conventional array must still win");
Assertions.assertTrue(VAOManager.get(0).genericPointer, "location must remain generic-sourced");
GLStateManager.glDisableVertexAttribArray(0);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
}
@Test
void testFfpConventionalAppliesWithoutGenericAttribute() {
final int vao = UniversalVAO.genVertexArrays();
glBindVertexArray(vao);
final int vbo = GL15.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glVertexPointer(3, GL11.GL_FLOAT, 0, 0L);
GLStateManager.glEnableClientState(GL11.GL_VERTEX_ARRAY);
Assertions.assertEquals(3, VAOManager.get(0).size, "plain FFP conventional array must apply when no generic attribute occupies the location");
Assertions.assertFalse(VAOManager.get(0).genericPointer, "location must be conventional-sourced");
GLStateManager.glDisableClientState(GL11.GL_VERTEX_ARRAY);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
GL15.glDeleteBuffers(vbo);
UniversalVAO.deleteVertexArrays(vao);
}
private static void testClientStates() {
verifyState(GL11.GL_VERTEX_ARRAY, false, "GL_VERTEX_ARRAY Default State");
verifyState(GL11.GL_TEXTURE_COORD_ARRAY, false, "GL_TEXTURE_COORD_ARRAY Default State");
@@ -133,7 +195,7 @@ public class GLSM_VAO_UnitTest {
private static void glBindVertexArray(int array) {
if (array == 0) array = GLStateManager.getDefaultVAO();
GLStateManager.boundVAO = array;
VertexAttribState.onBindVertexArray(array);
VAOManager.onBindVertexArrayPre(array);
UniversalVAO.bindVertexArray(array);
}
}
@@ -0,0 +1,34 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
class GLVersionParseTest {
@Test
void parseMajorMinor() {
assertEquals(43, RenderSystem.parseMajorMinor("4.3.0 - Build 20.19.15.4835"));
assertEquals(46, RenderSystem.parseMajorMinor("4.6.0 NVIDIA 551.61"));
assertEquals(41, RenderSystem.parseMajorMinor("4.1 Metal - 88"));
assertEquals(33, RenderSystem.parseMajorMinor("3.3"));
assertEquals(46, RenderSystem.parseMajorMinor("4.6 (Core Profile) Mesa 24.0.3"));
assertEquals(-1, RenderSystem.parseMajorMinor(null));
assertEquals(-1, RenderSystem.parseMajorMinor(""));
assertEquals(-1, RenderSystem.parseMajorMinor("garbage"));
assertEquals(-1, RenderSystem.parseMajorMinor("OpenGL ES 3.2"));
}
@Test
void isContextValid() {
assertTrue(RenderSystem.isContextValid(4, 4, 46, true));
assertTrue(RenderSystem.isContextValid(4, 4, 44, true));
assertFalse(RenderSystem.isContextValid(4, 4, 43, true));
assertFalse(RenderSystem.isContextValid(4, 4, 44, false));
assertTrue(RenderSystem.isContextValid(4, 4, -1, true));
assertFalse(RenderSystem.isContextValid(4, 4, -1, false));
assertTrue(RenderSystem.isContextValid(3, 3, 33, true));
}
}
@@ -0,0 +1,82 @@
package com.gtnewhorizons.angelica.glsm;
import org.joml.FrustumIntersection;
import org.joml.Matrix4f;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
class HugeFarPlaneFrustumTest {
private static final float SECTION_HALF = 8.0f + 1.0f + 0.125f;
private static final float DEGENERATE_FAR = 2.147483648e9f;
private static Matrix4f combined(float zFar) {
final Matrix4f proj = new Matrix4f().perspective((float) Math.toRadians(70.0), 854.0f / 480.0f, 0.05f, zFar);
final Matrix4f mv = new Matrix4f()
.rotate((float) Math.toRadians(25.0), 1, 0, 0)
.rotate((float) Math.toRadians(123.0), 0, 1, 0)
.translate(0.0f, -1.62f, 0.0f);
return new Matrix4f().set(proj).mul(mv);
}
private static boolean sectionVisible(FrustumIntersection f, int sx, int sy, int sz) {
final float cx = sx * 16.0f + 8.0f;
final float cy = sy * 16.0f + 8.0f;
final float cz = sz * 16.0f + 8.0f;
return f.testAab(cx - SECTION_HALF, cy - SECTION_HALF, cz - SECTION_HALF, cx + SECTION_HALF, cy + SECTION_HALF, cz + SECTION_HALF);
}
@Test
void unnormalizedFrustumMatchesNormalFarPlane() {
final FrustumIntersection normal = new FrustumIntersection(combined(512.0f), false);
final FrustumIntersection huge = new FrustumIntersection(combined(DEGENERATE_FAR), false);
int visible = 0;
int mismatches = 0;
for (int x = -12; x <= 12; x++) {
for (int y = -4; y <= 4; y++) {
for (int z = -12; z <= 12; z++) {
final boolean vNormal = sectionVisible(normal, x, y, z);
if (vNormal) visible++;
if (vNormal != sectionVisible(huge, x, y, z)) mismatches++;
}
}
}
assertTrue(visible > 100, "sanity check broken: normal frustum accepted only " + visible + " sections");
assertEquals(0, mismatches, "sections culled differently under the degenerate far plane");
}
@Test
void normalizedSetCullsEverythingOnDegenerateFarPlane() {
final FrustumIntersection normalized = new FrustumIntersection();
normalized.set(combined(DEGENERATE_FAR));
int visible = 0;
for (int x = -12; x <= 12; x++) {
for (int y = -4; y <= 4; y++) {
for (int z = -12; z <= 12; z++) {
if (sectionVisible(normalized, x, y, z)) visible++;
}
}
}
assertEquals(0, visible, "set(m) with a degenerate far plane no longer culls everything");
}
@Test
void identityFrustumOnlyPassesCameraSection() {
final FrustumIntersection identity = new FrustumIntersection(new Matrix4f());
assertTrue(sectionVisible(identity, 0, 0, 0), "identity frustum rejected the camera's own section");
int visible = 0;
for (int x = -12; x <= 12; x++) {
for (int y = -4; y <= 4; y++) {
for (int z = -12; z <= 12; z++) {
if (Math.abs(x) <= 1 && Math.abs(y) <= 1 && Math.abs(z) <= 1) continue;
if (sectionVisible(identity, x, y, z)) visible++;
}
}
}
assertEquals(0, visible, "non-adjacent sections accepted by the identity frustum");
}
}
@@ -0,0 +1,125 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizons.angelica.glsm.recording.CompiledDisplayList;
import com.gtnewhorizons.angelica.glsm.recording.GLCommand;
import com.gtnewhorizons.angelica.glsm.recording.ImmediateModeRecorder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(GLSMCoreExtension.class)
class ImmediateModeThreadIsolationTest {
@Test
void backgroundThreadsGetDistinctTessellators() throws Exception {
final DirectTessellator mainTess = ImmediateModeRecorder.getInternalTessellator();
final AtomicReference<DirectTessellator> bg1 = new AtomicReference<>();
final AtomicReference<DirectTessellator> bg2 = new AtomicReference<>();
final Thread t1 = new Thread(() -> bg1.set(ImmediateModeRecorder.getInternalTessellator()), "iso-test-1");
final Thread t2 = new Thread(() -> bg2.set(ImmediateModeRecorder.getInternalTessellator()), "iso-test-2");
t1.start(); t2.start();
t1.join(); t2.join();
assertNotNull(bg1.get());
assertNotNull(bg2.get());
assertNotSame(mainTess, bg1.get(), "background thread must not share the main-thread tessellator");
assertNotSame(mainTess, bg2.get(), "background thread must not share the main-thread tessellator");
assertNotSame(bg1.get(), bg2.get(), "background threads must not share a tessellator");
}
@Test
void concurrentImmediateModeDoesNotCorruptRecording() throws Exception {
final List<Throwable> failures = new CopyOnWriteArrayList<>();
final CountDownLatch start = new CountDownLatch(1);
final AtomicBoolean stop = new AtomicBoolean(false);
final Runnable spam = () -> {
try {
start.await();
while (!stop.get()) {
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glVertex3f(0, 0, 0);
GLStateManager.glVertex3f(1, 0, 0);
GLStateManager.glVertex3f(1, 1, 0);
GLStateManager.glVertex3f(0, 1, 0);
GLStateManager.glEnd();
}
} catch (Throwable t) {
failures.add(t);
}
};
final Thread t1 = new Thread(spam, "iso-spam-1");
final Thread t2 = new Thread(spam, "iso-spam-2");
t1.start(); t2.start();
start.countDown();
final List<Integer> lists = new ArrayList<>();
try {
for (int i = 0; i < 50; i++) {
final int list = GLStateManager.glGenLists(1);
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glBegin(GL11.GL_QUADS);
GLStateManager.glVertex3f(0, 0, 0);
GLStateManager.glVertex3f(1, 0, 0);
GLStateManager.glVertex3f(1, 1, 0);
GLStateManager.glVertex3f(0, 1, 0);
GLStateManager.glEnd();
GLStateManager.glEndList();
lists.add(list);
}
} finally {
stop.set(true);
t1.join(5000); t2.join(5000);
}
assertTrue(failures.isEmpty(), "background immediate mode threw: " + failures);
for (int list : lists) {
final CompiledDisplayList compiled = DisplayListManager.getDisplayList(list);
assertNotNull(compiled, "list " + list + " should be compiled");
assertEquals(1, compiled.getCommandCounts().getOrDefault(GLCommand.DRAW_RANGE, 0), "list " + list + " should contain exactly the main thread's draw");
GLStateManager.glDeleteLists(list, 1);
}
}
@Test
void sweepFreesDeadThreadTessellators() throws Exception {
final Thread t = new Thread(() -> ImmediateModeRecorder.getInternalTessellator(), "iso-sweep");
t.start();
t.join();
final int before = ImmediateModeRecorder.getThreadTessellatorCount();
assertTrue(before >= 1);
ImmediateModeRecorder.cleanupOrphanTessellators();
assertTrue(ImmediateModeRecorder.getThreadTessellatorCount() < before, "dead thread's tessellator should be swept");
}
@Test
void drawLockGatesOnSplashComplete() throws Exception {
assertFalse(GLStateManager.acquireDrawLock(), "post-splash acquire must be a no-op");
final Field f = GLStateManager.class.getDeclaredField("splashComplete");
f.setAccessible(true);
f.set(null, false);
try {
assertTrue(GLStateManager.acquireDrawLock(), "during splash the lock must engage");
GLStateManager.releaseDrawLock();
} finally {
f.set(null, true);
}
}
}
@@ -0,0 +1,105 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL20;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@ExtendWith(GLSMCoreExtension.class)
class InstancedQuadDrawGLTest {
private static final String VS = "#version 330 core\nlayout(location=0) in vec2 aPos;\nvoid main(){ gl_Position = vec4(aPos, 0.0, 1.0); }\n";
private static final String FS = "#version 330 core\nout vec4 o;\nvoid main(){ o = vec4(1.0); }\n";
private int program;
private int vbo;
private int ebo;
@BeforeEach
void setup() {
program = buildProgram();
GLStateManager.glUseProgram(program);
final FloatBuffer verts = BufferUtils.createFloatBuffer(8);
verts.put(new float[] {0, 0, 1, 0, 1, 1, 0, 1}).flip();
vbo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, vbo);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, verts, GL15.GL_STATIC_DRAW);
GLStateManager.glVertexAttribPointer(0, 2, GL11.GL_FLOAT, false, 0, 0L);
GLStateManager.glEnableVertexAttribArray(0);
final IntBuffer idx = BufferUtils.createIntBuffer(4);
idx.put(new int[] {0, 1, 2, 3}).flip();
ebo = GLStateManager.glGenBuffers();
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, idx, GL15.GL_STATIC_DRAW);
drainErrors();
}
@AfterEach
void cleanup() {
GLStateManager.glUseProgram(0);
if (program != 0) GL20.glDeleteProgram(program);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
if (vbo != 0) GLStateManager.glDeleteBuffers(vbo);
if (ebo != 0) GLStateManager.glDeleteBuffers(ebo);
drainErrors();
}
@Test
void drawArraysInstanced_quads_noInvalidEnum() {
GLStateManager.glDrawArraysInstanced(GL11.GL_QUADS, 0, 4, 3);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "GL_QUADS instanced array draw must be emulated, not rejected");
}
@Test
void drawArraysInstanced_triangles_fastPathClean() {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
GLStateManager.glDrawArraysInstanced(GL11.GL_TRIANGLES, 0, 3, 3);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "non-quad instanced array draw passes through cleanly");
}
@Test
void drawElementsInstanced_quads_noInvalidEnum() {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, ebo);
GLStateManager.glDrawElementsInstanced(GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_INT, 0L, 3);
assertEquals(GL11.GL_NO_ERROR, GL11.glGetError(), "GL_QUADS instanced element draw must be emulated, not rejected");
}
private static int buildProgram() {
final int vs = compile(GL20.GL_VERTEX_SHADER, VS);
final int fs = compile(GL20.GL_FRAGMENT_SHADER, FS);
final int p = GL20.glCreateProgram();
GL20.glAttachShader(p, vs);
GL20.glAttachShader(p, fs);
GL20.glLinkProgram(p);
assertNotEquals(0, GL20.glGetProgrami(p, GL20.GL_LINK_STATUS), "test shader must link: " + GL20.glGetProgramInfoLog(p, 1024));
GL20.glDeleteShader(vs);
GL20.glDeleteShader(fs);
return p;
}
private static int compile(int type, String src) {
final int s = GL20.glCreateShader(type);
GL20.glShaderSource(s, src);
GL20.glCompileShader(s);
assertNotEquals(0, GL20.glGetShaderi(s, GL20.GL_COMPILE_STATUS), "test shader must compile: " + GL20.glGetShaderInfoLog(s, 1024));
return s;
}
private static void drainErrors() {
while (GL11.glGetError() != GL11.GL_NO_ERROR) { /* clear */ }
}
}
@@ -0,0 +1,83 @@
package com.gtnewhorizons.angelica.glsm;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.ContextAttribs;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.PixelFormat;
import org.lwjgl.opengl.SharedDrawable;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
class SharedDrawableVaoTest {
private static final String VERT_330 = "#version 330 core\nvoid main() { gl_Position = vec4(0.0); }\n";
private static final String FRAG_330 = "#version 330 core\nout vec4 o;\nvoid main() { o = vec4(0.0); }\n";
private static boolean ownsDisplay = false;
@BeforeAll
static void createContext() throws LWJGLException {
assumeTrue(!Display.isCreated(), "inherited Display -- skipping to avoid profile drift");
Display.setDisplayModeAndFullscreen(new DisplayMode(800, 600));
Display.setResizable(false);
Display.setFullscreen(false);
Display.create(
new PixelFormat().withDepthBits(24).withStencilBits(8),
new ContextAttribs(4, 1).withProfileCore(true).withForwardCompatible(true));
GL30.glBindVertexArray(GL30.glGenVertexArrays());
ownsDisplay = true;
}
@AfterAll
static void destroyContext() {
if (ownsDisplay && Display.isCreated()) Display.destroy();
ownsDisplay = false;
}
@Test
void validateProgramSucceedsOnSharedDrawableWithVao() throws LWJGLException {
SharedDrawable shared = new SharedDrawable(Display.getDrawable());
Display.getDrawable().releaseContext();
shared.makeCurrent();
try {
GL30.glBindVertexArray(GL30.glGenVertexArrays());
int program = linkTrivialProgram();
GL20.glValidateProgram(program);
assertEquals(GL11.GL_TRUE, GL20.glGetProgrami(program, GL20.GL_VALIDATE_STATUS), () -> GL20.glGetProgramInfoLog(program, 4096));
GL20.glDeleteProgram(program);
} finally {
shared.releaseContext();
Display.getDrawable().makeCurrent();
shared.destroy();
}
}
private static int linkTrivialProgram() {
int vs = compile(GL20.GL_VERTEX_SHADER, VERT_330);
int fs = compile(GL20.GL_FRAGMENT_SHADER, FRAG_330);
int program = GL20.glCreateProgram();
GL20.glAttachShader(program, vs);
GL20.glAttachShader(program, fs);
GL20.glLinkProgram(program);
assertEquals(GL11.GL_TRUE, GL20.glGetProgrami(program, GL20.GL_LINK_STATUS), () -> GL20.glGetProgramInfoLog(program, 4096));
GL20.glDeleteShader(vs);
GL20.glDeleteShader(fs);
return program;
}
private static int compile(int type, String src) {
int shader = GL20.glCreateShader(type);
GL20.glShaderSource(shader, src);
GL20.glCompileShader(shader);
assertEquals(GL11.GL_TRUE, GL20.glGetShaderi(shader, GL20.GL_COMPILE_STATUS), () -> GL20.glGetShaderInfoLog(shader, 4096));
return shader;
}
}
@@ -28,7 +28,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlColor4fSetsVertexAttrib() {
GLStateManager.glColor4f(0.5f, 0.25f, 0.75f, 0.8f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.5f, color[0], EPSILON, "color.r");
@@ -40,7 +40,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testGlNormal3fSetsVertexAttrib() {
GLStateManager.glNormal3f(0.0f, 1.0f, 0.0f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] normal = getVertexAttrib4f(NORMAL_ATTRIB);
assertEquals(0.0f, normal[0], EPSILON, "normal.x");
@@ -51,7 +51,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testSetLightmapTextureCoordsOnUnit1SetsVertexAttrib() {
GLStateManager.setLightmapTextureCoords(GL13.GL_TEXTURE1, 240.0f, 128.0f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] lm = getVertexAttrib4f(SECONDARY_UV_ATTRIB);
assertEquals(240.0f, lm[0], EPSILON, "lightmap.s");
@@ -64,7 +64,7 @@ class VertexAttribDefaultBridgeTest {
void testGlTexCoord2fSetsVertexAttrib() {
// glTexCoord2f outside recording → sets default vertex attrib 2 (PRIMARY_UV)
GLStateManager.glTexCoord2f(0.5f, 0.75f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] uv = getVertexAttrib4f(PRIMARY_UV_ATTRIB);
assertEquals(0.5f, uv[0], EPSILON, "texcoord.s");
@@ -77,7 +77,7 @@ class VertexAttribDefaultBridgeTest {
// Second call is a cache hit — changeColor not called.
// Vertex attrib should still hold the correct value from the first call.
GLStateManager.glColor4f(0.3f, 0.6f, 0.9f, 1.0f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
float[] color = getVertexAttrib4f(COLOR_ATTRIB);
assertEquals(0.3f, color[0], EPSILON, "color.r after dedup");
@@ -89,7 +89,7 @@ class VertexAttribDefaultBridgeTest {
@Test
void testCompileMode_DoesNotSetVertexAttrib() {
GLStateManager.glColor4f(0.1f, 0.2f, 0.3f, 1.0f);
GLStateManager.flushDeferredVertexAttribs();
GLStateManager.flushDeferredVertexAttribs(false, false, false, false);
int listId = GLStateManager.glGenLists(1);
GLStateManager.glNewList(listId, org.lwjgl.opengl.GL11.GL_COMPILE);
@@ -28,6 +28,7 @@ class FFPCombineShaderTest {
private final List<Integer> shadersToDelete = new ArrayList<>();
private final List<Integer> programsToDelete = new ArrayList<>();
private final List<Integer> texturesToDelete = new ArrayList<>();
@AfterEach
void cleanup() {
@@ -37,15 +38,25 @@ class FFPCombineShaderTest {
programsToDelete.clear();
shadersToDelete.clear();
// Reset texture units to defaults
// Reset texture units to defaults (unbind so a later test does not see a stale binding)
for (int i = 3; i >= 0; i--) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + i);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
GLStateManager.getTextures().getTexEnvState(i).reset();
if (i > 0) {
GLStateManager.getTextures().getTextureUnitStates(i).disable();
}
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
for (int t : texturesToDelete) GL11.glDeleteTextures(t);
texturesToDelete.clear();
}
private int bindUnitTexture() {
final int tex = GL11.glGenTextures();
texturesToDelete.add(tex);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
return tex;
}
@Test
@@ -113,6 +124,7 @@ class FFPCombineShaderTest {
void testSingleUnitCombineCompiles() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
@@ -128,7 +140,7 @@ class FFPCombineShaderTest {
setupXaeroState();
FragmentKey fk = FragmentKey.fromState();
VertexKey vk = VertexKey.fromState(true, false, true, false);
VertexKey vk = VertexKey.fromState(true, false, true, false, fk.enabledUnitMask());
String fragSource = FragmentShaderGenerator.generate(fk);
String vertSource = VertexShaderGenerator.generate(vk);
@@ -169,6 +181,7 @@ class FFPCombineShaderTest {
void testAllCombineFunctionsCompile(String name, int glFunc, int numArgs) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, glFunc);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
@@ -202,6 +215,7 @@ class FFPCombineShaderTest {
void testCombineScopeDeclaresColorOutsideBlock() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_MODULATE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_MODULATE);
@@ -258,6 +272,7 @@ class FFPCombineShaderTest {
// Unit 0: COMBINE / SUBTRACT
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL13.GL_SUBTRACT);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
@@ -273,6 +288,7 @@ class FFPCombineShaderTest {
// Unit 2: COMBINE / ADD
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL13.GL_COMBINE);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_RGB, GL11.GL_ADD);
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL13.GL_COMBINE_ALPHA, GL11.GL_REPLACE);
@@ -286,6 +302,7 @@ class FFPCombineShaderTest {
// Unit 3: simple MODULATE
GLStateManager.glActiveTexture(GL13.GL_TEXTURE3);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glTexEnvi(GL11.GL_TEXTURE_ENV, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_MODULATE);
// Restore active unit
@@ -0,0 +1,72 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLSMCoreExtension;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ExtendWith(GLSMCoreExtension.class)
class FFPUnboundTextureUnitTest {
private final List<Integer> texturesToDelete = new ArrayList<>();
@AfterEach
void cleanup() {
for (int i = 3; i >= 0; i--) {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0 + i);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, 0);
if (i > 0) GLStateManager.getTextures().getTextureUnitStates(i).disable();
}
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
for (int t : texturesToDelete) GL11.glDeleteTextures(t);
texturesToDelete.clear();
}
private void bindUnitTexture() {
final int tex = GL11.glGenTextures();
texturesToDelete.add(tex);
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, tex);
}
@Test
void enabledUnitWithoutTextureIsExcluded() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.enableTexture(); // enabled, nothing bound
GLStateManager.glActiveTexture(GL13.GL_TEXTURE3);
GLStateManager.enableTexture(); // enabled, nothing bound
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final FragmentKey fk = FragmentKey.fromState();
assertEquals(1, fk.nrEnabledUnits(), "units 2/3 have no texture bound -> excluded -> only unit 0");
assertEquals(0b0001, fk.enabledUnitMask(), "only unit 0 in the key; enabled-but-unbound units 2/3 excluded");
assertTrue(fk.unitEnabled(0), "unit 0 (texture bound) stays enabled");
}
@Test
void enabledUnitsWithTextureAreCounted() {
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE2);
GLStateManager.enableTexture();
bindUnitTexture();
GLStateManager.glActiveTexture(GL13.GL_TEXTURE0);
final FragmentKey fk = FragmentKey.fromState();
assertTrue(fk.unitEnabled(0));
assertTrue(fk.unitEnabled(2), "unit 2 with a bound texture must be counted");
assertEquals(3, fk.nrEnabledUnits(), "highest enabled is unit 2 -> nrEnabledUnits=3");
}
}
@@ -0,0 +1,63 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLSMCoreExtension;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
@ExtendWith(GLSMCoreExtension.class)
class ProgramUniformTrackingTest {
private static Program buildProgram(boolean hasColor) {
final VertexKey vk = VertexKey.fromState(hasColor, false, false, false, 0);
final FragmentKey fk = FragmentKey.fromState();
return Program.create(vk, fk, VertexShaderGenerator.generate(vk), FragmentShaderGenerator.generate(fk), null);
}
@Test
void matrixTrackingIsPerProgram() {
final Uniforms uniforms = new Uniforms();
final Program a = buildProgram(true);
final Program b = buildProgram(false);
assertNotEquals(a.getProgramId(), b.getProgramId(), "distinct vertex keys must yield distinct programs");
try {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
GLStateManager.glPushMatrix();
GLStateManager.glLoadIdentity();
GLStateManager.glTranslatef(1.0F, 2.0F, 3.0F);
final int genA = GLStateManager.mvGeneration;
GL20.glUseProgram(a.getProgramId());
uniforms.upload(a);
assertEquals(genA, a.uploadState.mvGen, "A records the mv generation it uploaded");
GLStateManager.glTranslatef(4.0F, 0.0F, 0.0F);
final int genB = GLStateManager.mvGeneration;
assertNotEquals(genA, genB);
GL20.glUseProgram(b.getProgramId());
uniforms.upload(b);
assertEquals(genB, b.uploadState.mvGen, "B records the new mv generation");
assertEquals(genA, a.uploadState.mvGen, "A is unchanged -- tracking is per-program, not global");
// Return to A with the modelview still at genB: A is stale -> re-uploads and catches up.
GL20.glUseProgram(a.getProgramId());
uniforms.upload(a);
assertEquals(genB, a.uploadState.mvGen, "A re-uploads on return and catches up to genB");
// Re-upload A with no change: gate (mvGeneration == A.mvGen) is satisfied -> skip.
uniforms.upload(a);
assertEquals(genB, a.uploadState.mvGen, "A already current -> skipped");
GLStateManager.glPopMatrix();
} finally {
GL20.glUseProgram(0);
a.destroy();
b.destroy();
}
}
}
@@ -3,12 +3,15 @@ package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.BatchedIndexedDrawCmd;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
import com.gtnewhorizons.angelica.glsm.recording.support.DisplayListTestFixture;
import com.gtnewhorizons.angelica.glsm.states.VertexAttribState;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.EnumSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
@@ -17,7 +20,10 @@ import org.lwjgl.opengl.GL30;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.nio.ShortBuffer;
import java.util.List;
import java.util.stream.Stream;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
@@ -25,6 +31,7 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.params.provider.Arguments.arguments;
class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
@@ -307,10 +314,10 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
final ByteBuffer colorSlice = clientData.duplicate().order(ByteOrder.nativeOrder());
colorSlice.position(8);
VertexAttribState.set(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, clientData, 0);
VertexAttribState.setEnabled(0, true);
VertexAttribState.set(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, colorSlice, 0);
VertexAttribState.setEnabled(1, true);
VAOManager.setAttribute(0, 2, GL11.GL_FLOAT, false, POS_COLOR_STRIDE, clientData);
VAOManager.enableAttribute(0);
VAOManager.setAttribute(1, 4, GL11.GL_UNSIGNED_BYTE, true, POS_COLOR_STRIDE, colorSlice);
VAOManager.enableAttribute(1);
GLStateManager.glBindVertexArray(0);
final int ebo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
@@ -521,7 +528,153 @@ class IndexedDrawBatchBuilderTest extends DisplayListTestFixture {
"listBA: batch[1] must match the second-recorded layout");
}
// === local helpers ===
private static Stream<Arguments> clientIndexedDrawCases() {
return Stream.of(
arguments("byte indices", GL11.GL_COMPILE, (Runnable) () -> {
final ByteBuffer idx = BufferUtils.createByteBuffer(3);
idx.put(new byte[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3),
arguments("short indices", GL11.GL_COMPILE, (Runnable) () -> {
final ShortBuffer idx = BufferUtils.createShortBuffer(3);
idx.put(new short[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3),
arguments("int indices", GL11.GL_COMPILE, (Runnable) () -> {
final IntBuffer idx = BufferUtils.createIntBuffer(3);
idx.put(new int[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3),
arguments("explicit count+type", GL11.GL_COMPILE, (Runnable) () -> {
final ByteBuffer idx = BufferUtils.createByteBuffer(3 * 2).order(ByteOrder.nativeOrder());
idx.putShort((short) 0).putShort((short) 1).putShort((short) 2).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, idx);
}, GL11.GL_TRIANGLES, 3),
arguments("quads triangulate at record", GL11.GL_COMPILE, (Runnable) () -> {
final IntBuffer idx = BufferUtils.createIntBuffer(4);
idx.put(new int[]{0, 1, 2, 3}).flip();
GLStateManager.glDrawElements(GL11.GL_QUADS, idx);
}, GL11.GL_TRIANGLES, 6),
arguments("compile-and-execute fires live and bakes", GL11.GL_COMPILE_AND_EXECUTE, (Runnable) () -> {
final IntBuffer idx = BufferUtils.createIntBuffer(3);
idx.put(new int[]{0, 1, 2}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}, GL11.GL_TRIANGLES, 3)
);
}
@ParameterizedTest(name = "{0}")
@MethodSource("clientIndexedDrawCases")
void clientIndexedDraw_capturedAndWidenedToUnsignedInt(String name, int listMode, Runnable draw, int expectedMode, int expectedCount) {
final int list = recordClientIndexDraw(listMode, draw);
final BatchedIndexedDrawCmd cmd = firstBatchedCmd(list);
assertNotNull(cmd);
assertEquals(expectedMode, cmd.getDrawMode());
assertEquals(expectedCount, cmd.getIndexCount());
assertEquals(GL11.GL_UNSIGNED_INT, cmd.getIndexType(), "client indices always widen to UNSIGNED_INT");
}
@Test
void clientVerticesAndIndices_pointersSetDuringRecording_bake() {
final float[][] verts = {{0f, 0f, 0f}, {1f, 0f, 0f}, {1f, 1f, 0f}, {0f, 1f, 0f}};
final ByteBuffer clientVerts = BufferUtils.createByteBuffer(verts.length * 12).order(ByteOrder.nativeOrder());
for (float[] v : verts) {
clientVerts.putFloat(v[0]);
clientVerts.putFloat(v[1]);
clientVerts.putFloat(v[2]);
}
clientVerts.flip();
final IntBuffer indices = BufferUtils.createIntBuffer(6);
indices.put(new int[]{0, 1, 2, 2, 3, 0}).flip();
final int vao = newVao();
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, 0);
VAOManager.setAttribute(0, 3, GL11.GL_FLOAT, false, 0, clientVerts);
VAOManager.enableAttribute(0);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, indices);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
final BatchedIndexedDrawCmd cmd = firstBatchedCmd(list);
assertNotNull(cmd);
assertEquals(GL11.GL_TRIANGLES, cmd.getDrawMode());
assertEquals(6, cmd.getIndexCount());
assertEquals(GL11.GL_UNSIGNED_INT, cmd.getIndexType());
final ByteBuffer owned = readBufferBytes(GL15.GL_ARRAY_BUFFER, firstBatch(list).getSharedVBO(), verts.length * 12);
for (int i = 0; i < verts.length; i++) {
assertEquals(verts[i][0], owned.getFloat(i * 12), 0f, "pos.x v=" + i);
assertEquals(verts[i][1], owned.getFloat(i * 12 + 4), 0f, "pos.y v=" + i);
assertEquals(verts[i][2], owned.getFloat(i * 12 + 8), 0f, "pos.z v=" + i);
}
MemoryUtilities.memFree(owned);
}
@Test
void quadOffsetDraw_compileAndExecute_doesNotPauseRecordingForRestOfList() {
final int vao = setupPosColorVao(defaultQuadVbo());
final int quadEbo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2, 3);
final int triEbo = uploadEbo(GL11.GL_UNSIGNED_SHORT, 0, 1, 2);
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, quadEbo);
GLStateManager.glDrawElements(GL11.GL_QUADS, 4, GL11.GL_UNSIGNED_SHORT, 0L);
assertTrue(DisplayListManager.isRecording(), "recording must resume after a GL_QUADS offset draw");
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, triEbo);
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 3, GL11.GL_UNSIGNED_SHORT, 0L);
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
assertEquals(2, allBatchedCmds(list).size(), "both the QUADS draw and the following triangle draw must be recorded");
}
private enum MalformedClientDraw { OVERSIZED_COUNT, INDEX_PAST_VERTEX_BUFFER }
@ParameterizedTest(name = "{0}")
@EnumSource(MalformedClientDraw.class)
void malformedClientDraw_skippedAndRecordingSurvives(MalformedClientDraw scenario) {
final int list = newList();
GLStateManager.glNewList(list, GL11.GL_COMPILE);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
switch (scenario) {
case OVERSIZED_COUNT -> {
GLStateManager.glBindVertexArray(setupPosColorVao(defaultQuadVbo()));
final ByteBuffer idx = BufferUtils.createByteBuffer(3 * 2).order(ByteOrder.nativeOrder());
idx.putShort((short) 0).putShort((short) 1).putShort((short) 2).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, 100, GL11.GL_UNSIGNED_SHORT, idx);
}
case INDEX_PAST_VERTEX_BUFFER -> {
final int vbo = uploadPosColorVbo(new float[]{0f, 0f, 1f, 0f}, new byte[][]{{1, 1, 1, 1}, {2, 2, 2, 2}});
GLStateManager.glBindVertexArray(setupPosColorVao(vbo));
final IntBuffer idx = BufferUtils.createIntBuffer(4);
idx.put(new int[]{0, 1, 2, 3}).flip();
GLStateManager.glDrawElements(GL11.GL_TRIANGLES, idx);
}
}
assertTrue(DisplayListManager.isRecording(), "recording survives a skipped malformed draw");
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
assertNull(firstBatchedCmd(list), "malformed draw must be skipped, not captured or over-read");
}
private int recordClientIndexDraw(int listMode, Runnable draw) {
final int vao = setupPosColorVao(defaultQuadVbo());
final int list = newList();
GLStateManager.glNewList(list, listMode);
GLStateManager.glBindVertexArray(vao);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, 0);
draw.run();
GLStateManager.glBindVertexArray(0);
GLStateManager.glEndList();
return list;
}
private static void assertUintIndices(ByteBuffer ib, int... expected) {
for (int i = 0; i < expected.length; i++) {
@@ -207,7 +207,7 @@ class IndexedDrawReplayTest extends DisplayListTestFixture {
new AttribLayoutKey(new int[]{0}, new int[]{2}, new int[]{GL11.GL_FLOAT}, new boolean[]{false}),
GL11.GL_TRIANGLES, 4, 6, vtx, idx, new BatchedIndexedDrawCmd());
rec.recordIndexedDrawCapture(cap);
rec.writeIndexedDrawCapture(cap);
assertFalse(cap.isFreed());
rec.free();
@@ -0,0 +1,61 @@
package com.gtnewhorizons.angelica.glsm.states;
import org.junit.jupiter.api.Test;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
class PixelUnpackStateTest {
@Test
void defaults() {
assertTrue(PixelUnpackState.DEFAULT.isDefault());
assertEquals(4, PixelUnpackState.DEFAULT.alignment());
assertEquals(0, PixelUnpackState.DEFAULT.rowLength());
assertFalse(PixelUnpackState.DEFAULT.swapBytes());
assertFalse(PixelUnpackState.DEFAULT.lsbFirst());
}
@Test
void withUpdatesUnpackParams() {
PixelUnpackState s = PixelUnpackState.DEFAULT
.with(GL11.GL_UNPACK_ALIGNMENT, 1)
.with(GL11.GL_UNPACK_ROW_LENGTH, 64)
.with(GL11.GL_UNPACK_SKIP_ROWS, 2)
.with(GL11.GL_UNPACK_SKIP_PIXELS, 3)
.with(GL12.GL_UNPACK_IMAGE_HEIGHT, 32)
.with(GL12.GL_UNPACK_SKIP_IMAGES, 1)
.with(GL11.GL_UNPACK_SWAP_BYTES, 1)
.with(GL11.GL_UNPACK_LSB_FIRST, 1);
assertEquals(new PixelUnpackState(1, 64, 2, 3, 32, 1, true, true), s);
assertFalse(s.isDefault());
}
@Test
void invalidValuesIgnored() {
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 3));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ALIGNMENT, 0));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_ROW_LENGTH, -1));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SKIP_ROWS, -5));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL12.GL_UNPACK_IMAGE_HEIGHT, -1));
}
@Test
void booleansAcceptAnyNonzero() {
assertTrue(PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 2).swapBytes());
assertTrue(PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_LSB_FIRST, -1).lsbFirst());
PixelUnpackState on = PixelUnpackState.DEFAULT.with(GL11.GL_UNPACK_SWAP_BYTES, 1);
assertFalse(on.with(GL11.GL_UNPACK_SWAP_BYTES, 0).swapBytes());
}
@Test
void packAndUnknownPnamesIgnored() {
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_PACK_ALIGNMENT, 1));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(GL11.GL_PACK_ROW_LENGTH, 16));
assertSame(PixelUnpackState.DEFAULT, PixelUnpackState.DEFAULT.with(0x1234, 1));
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 216 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 192 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 316 B

+6 -1
View File
@@ -34,6 +34,10 @@ repositories {
name = "GTNH Maven"
url = uri("https://nexus.gtnewhorizons.com/repository/public/")
}
maven {
name = "Minecraft Libraries"
url = uri("https://libraries.minecraft.net")
}
maven {
name = "Forge"
url = uri("https://maven.minecraftforge.net/")
@@ -57,7 +61,7 @@ dependencies {
api(project(":glsm"))
compileOnly("com.github.GTNewHorizons:lwjgl3ify:3.0.15:dev") { isTransitive = false }
compileOnly("org.embeddedt.celeritas:celeritas-common:2.5.3-GTNH") { isTransitive = false }
compileOnly("org.embeddedt.celeritas:celeritas-common:2.5.5-GTNH") { isTransitive = false }
val osName = System.getProperty("os.name").lowercase()
val osArch = System.getProperty("os.arch").lowercase()
@@ -74,6 +78,7 @@ dependencies {
// LWJGL3
implementation("org.lwjgl:lwjgl:${lwjglVersion}")
implementation("org.lwjgl:lwjgl-opengl:${lwjglVersion}")
compileOnly("org.lwjgl:lwjgl-sdl:${lwjglVersion}")
runtimeOnly("org.lwjgl:lwjgl:$lwjglVersion:$lwjglNatives")
runtimeOnly("org.lwjgl:lwjgl-opengl:$lwjglVersion:$lwjglNatives")
@@ -23,6 +23,9 @@ import org.lwjgl.opengl.GL44C;
import org.lwjgl.opengl.GL45C;
import org.lwjgl.opengl.GLCapabilities;
import org.lwjgl.opengl.GLDebugMessageCallback;
import org.lwjgl.sdl.SDL_DropEvent;
import org.lwjgl.sdl.SDL_EventFilter;
import org.lwjgl.sdl.SDLEvents;
import org.lwjgl.system.MemoryStack;
import java.nio.ByteBuffer;
@@ -31,6 +34,10 @@ import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.ShortBuffer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;
/**
* LWJGL3 GL implementation of {@link RenderBackend}.
@@ -43,6 +50,10 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
private GLDebugMessageCallback debugCallback;
private boolean debugOutputActive;
private SDL_EventFilter dropEventFilter;
private final ConcurrentLinkedQueue<String> droppedFiles = new ConcurrentLinkedQueue<>();
private volatile boolean watchingDrops;
@Override
public void init() {
caps = GL.getCapabilities();
@@ -50,7 +61,49 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public void shutdown() {
// no-op
stopFileDrop();
}
@Override
public boolean supportsFileDrop() {
return true;
}
@Override
public void startFileDrop() {
if (watchingDrops) return;
dropEventFilter = SDL_EventFilter.create((userData, eventPtr) -> {
if (SDL_DropEvent.ntype(eventPtr) == SDLEvents.SDL_EVENT_DROP_FILE) {
final String path = SDL_DropEvent.ndataString(eventPtr);
if (path != null && !path.isEmpty()) {
droppedFiles.add(path);
}
}
return true;
});
SDLEvents.SDL_AddEventWatch(dropEventFilter, 0L);
watchingDrops = true;
}
@Override
public void stopFileDrop() {
if (!watchingDrops) return;
SDLEvents.SDL_RemoveEventWatch(dropEventFilter, 0L);
dropEventFilter.free();
dropEventFilter = null;
droppedFiles.clear();
watchingDrops = false;
}
@Override
public List<String> pollDroppedFiles() {
if (droppedFiles.isEmpty()) return Collections.emptyList();
final List<String> out = new ArrayList<>();
String p;
while ((p = droppedFiles.poll()) != null) {
out.add(p);
}
return out;
}
@Override
@@ -295,6 +348,16 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL31C.glDrawElementsInstanced(mode, count, type, indices, primcount);
}
@Override
public void drawArraysInstanced(int mode, int first, int count, int primcount) {
GL31C.glDrawArraysInstanced(mode, first, count, primcount);
}
@Override
public void provokingVertex(int provokeMode) {
GL32C.glProvokingVertex(provokeMode);
}
@Override
public void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride) {
GL43C.glMultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
@@ -491,11 +554,6 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL11C.glPixelStorei(pname, param);
}
@Override
public void pixelStoref(int pname, float param) {
GL11C.glPixelStoref(pname, param);
}
@Override
public void sampleCoverage(float value, boolean invert) {
GL13C.glSampleCoverage(value, invert);
@@ -601,6 +659,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL11C.glGetTexImage(target, level, format, type, pixels);
}
@Override
public void getTexImage(int target, int level, int format, int type, long pixelBufferOffset) {
GL11C.glGetTexImage(target, level, format, type, pixelBufferOffset);
}
@Override
public int getFramebufferAttachmentParameteri(int target, int attachment, int pname) {
return GL30C.glGetFramebufferAttachmentParameteri(target, attachment, pname);
@@ -808,6 +871,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL20C.glUniform4i(location, v0, v1, v2, v3);
}
@Override
public void uniform2(int location, FloatBuffer value) {
GL20C.glUniform2fv(location, value);
}
@Override
public void uniform3(int location, FloatBuffer value) {
GL20C.glUniform3fv(location, value);
@@ -818,6 +886,26 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL20C.glUniform4fv(location, value);
}
@Override
public void uniform1iv(int location, IntBuffer value) {
GL20C.glUniform1iv(location, value);
}
@Override
public void uniform2iv(int location, IntBuffer value) {
GL20C.glUniform2iv(location, value);
}
@Override
public void uniform3iv(int location, IntBuffer value) {
GL20C.glUniform3iv(location, value);
}
@Override
public void uniform4iv(int location, IntBuffer value) {
GL20C.glUniform4iv(location, value);
}
@Override
public void uniformMatrix3(int location, boolean transpose, FloatBuffer value) {
GL20C.glUniformMatrix3fv(location, transpose, value);
@@ -1013,6 +1101,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
return GL30C.glMapBufferRange(target, offset, length, access);
}
@Override
public void flushMappedBufferRange(int target, long offset, long length) {
GL30C.glFlushMappedBufferRange(target, offset, length);
}
@Override
public int genVertexArrays() {
return GL30C.glGenVertexArrays();
@@ -1028,6 +1121,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
GL30C.glBindVertexArray(array);
}
@Override
public boolean isVertexArray(int array) {
return GL30C.glIsVertexArray(array);
}
@Override
public void vertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer) {
GL20C.glVertexAttribPointer(index, size, type, normalized, stride, pointer);
@@ -1193,6 +1291,11 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
return GL45C.glCreateBuffers();
}
@Override
public void namedBufferData(int buffer, long size, int usage) {
GL45C.glNamedBufferData(buffer, size, usage);
}
@Override
public void namedBufferData(int buffer, ByteBuffer data, int usage) {
GL45C.glNamedBufferData(buffer, data, usage);
@@ -1318,11 +1421,14 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public boolean supportsDebugOutput() {
return caps != null && caps.OpenGL43;
return caps != null && (caps.OpenGL43 || caps.GL_KHR_debug);
}
@Override
public int setupDebugOutput(DebugMessageHandler handler) {
if (caps == null || !(caps.OpenGL43 || caps.GL_KHR_debug)) {
return 0;
}
debugCallback = GLDebugMessageCallback.create(
(source, type, id, severity, length, message, userParam) -> {
final String msg = GLDebugMessageCallback.getMessage(length, message);
@@ -1350,6 +1456,9 @@ public final class Lwjgl3GLRenderBackend extends RenderBackend {
@Override
public int disableDebugOutput() {
if (caps == null || !(caps.OpenGL43 || caps.GL_KHR_debug)) {
return 0;
}
GL43C.glDebugMessageCallback(null, 0L);
if (debugCallback != null) {
debugCallback.free();
@@ -0,0 +1,40 @@
package com.gtnewhorizons.angelica.lwjgl3;
import static org.lwjgl.sdl.SDLMessageBox.SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT;
import static org.lwjgl.sdl.SDLMessageBox.SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT;
import static org.lwjgl.sdl.SDLMessageBox.SDL_MESSAGEBOX_ERROR;
import static org.lwjgl.sdl.SDLMessageBox.SDL_ShowMessageBox;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import me.eigenraven.lwjgl3ify.client.MainThreadExec;
import org.lwjgl.sdl.SDL_MessageBoxButtonData;
import org.lwjgl.sdl.SDL_MessageBoxData;
import org.lwjgl.system.MemoryStack;
@Lwjgl3Aware
public final class MissingDependencySdl {
private MissingDependencySdl() {}
public static void showFatal(String title, String message) {
MainThreadExec.runOnMainThread(() -> showFatalOnMainThread(title, message));
}
private static void showFatalOnMainThread(String title, String message) {
try (MemoryStack stack = MemoryStack.stackPush()) {
final SDL_MessageBoxData box = SDL_MessageBoxData.calloc(stack);
box.flags(SDL_MESSAGEBOX_ERROR);
box.title(stack.UTF8(title));
box.message(stack.UTF8(message));
final SDL_MessageBoxButtonData.Buffer buttons = SDL_MessageBoxButtonData.calloc(1, stack);
buttons.get(0).set(
SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT | SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT,
0,
stack.UTF8("OK"));
box.buttons(buttons);
SDL_ShowMessageBox(box, null);
}
}
}
+1 -1
View File
@@ -16,7 +16,7 @@ pluginManagement {
}
plugins {
id("com.gtnewhorizons.gtnhsettingsconvention") version ("2.0.24")
id("com.gtnewhorizons.gtnhsettingsconvention") version ("2.0.26")
}
include("glsm")
Loaded 100 of 520 files, more files were not shown because too many files have changed in this diff. Show more