Compare commits

...
210 Commits
Author SHA1 Message Date
Jason Mitchell 3659b59db9 Adjust vsync and fps limiters, and fix WSR with player models (#2002) 2026-08-09 20:06:53 -07:00
Jason Mitchell 8c31eec525 SDL followup fixes (#2001)
* Deploy fixes
* Narrow transformer exclusions for Legends mods, and mixin priority for CustomNPC
* Journeymap 6 stencil/depth fix
* OpenLTW swizzle workaround
2026-08-09 13:43:46 -07:00
Jason Mitchell 166116d692 More fixes (#1995) 2026-08-08 23:53:06 -07:00
Jason Mitchell 4b21bc77b6 A few fixes (#1994) 2026-08-08 18:29:26 -07:00
Jason Mitchell 8da561d3b6 SDL GPU backend, TESR Batching/caching API, Tracy Integration (#1993) 2026-08-08 15:53:58 -07:00
ah-OOG-ah 413df76ad4 Fix compat for the original battlegear 2 (#1992)
* Fix compat for the original battlegear 2

* oops
2026-08-07 15:54:59 -04:00
boubou19 18fe05eda8 Do not concat strings from glCallList when debug is off (#1988) 2026-08-04 13:37:17 -07:00
ah-OOG-ah 4fa8cfe3e4 Bump Gradle and 3ify dep (#1986) 2026-08-04 05:05:23 +00:00
Eclipse-Sol b5dd1c04ce Generalize non-terrain block's material ID setting (#1981) 2026-08-02 13:10:38 -07:00
Algent 6c5eedb7c1 Optimize item frame rendering (#1964) 2026-08-01 20:16:16 -07:00
Eldrinn-Elantey 9d5eb01091 Remove dead getCharWidth ModifyConstant (#1983) 2026-08-01 16:01:10 -07:00
XLin0mu 0d54a65026 Fix Mekanism crash when it cast chunkTileEntityMap to HashMap (#1982) 2026-08-01 21:41:28 +00:00
Midnight145 8148d8f927 RenderChunkEvent for fluxloading to avoid mixin (#1974) 2026-08-01 14:27:25 -07:00
Jason Mitchell 5c441e4418 Stupid splash thread (#1973) 2026-07-28 18:39:27 -07:00
Jason Mitchell 78562f12d9 Do less work (#1971) 2026-07-28 15:24:36 -07:00
CRIMSTAL 031456f224 fix(DisplayListVBO): guard against null VAO and zero-count draw during display list replay (#1969) 2026-07-28 13:28:59 -07:00
Jason Mitchell ff35c684ca Cache a few more things on glsm (#1970) 2026-07-28 13:28:25 -07:00
Eclipse-Sol 5c4ef022d5 Make mipmap attribution hook not suck and thread-safe (#1967) 2026-07-27 19:14:19 -07:00
Jason Mitchell 4eb837a2c0 Chunk -> world coords (#1966) 2026-07-27 10:54:26 -07:00
Jason Mitchell 6570b6a0a5 Properly handle flags removed in core (#1965) 2026-07-26 21:46:30 -07:00
Jason Mitchell 493b641d56 Switch to an offset based cloud adjustment for World Providers that adjust it (#1962) 2026-07-25 15:35:40 -07:00
Eclipse-Sol 3f4995466c Fix scrollbar not following mouse (#1961)
* Fix scrollbar not following mouse

* add /*deobfName*/
2026-07-24 21:50:46 -04:00
Eclipse-Sol 56d7568fd3 Attempt at making mipmaps better (#1952) 2026-07-24 17:54:45 -07:00
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
Jason Mitchell e01097264b Fix infinite extent AABB crash w/ kotlin, and ensure proper sizing/padding on VAO/VBO snapshots. (#1687) 2026-04-20 10:11:46 -07:00
Jason Mitchell 0a922f3129 AE2 Crash Fix (#1684)
* Snapshot all relevant data into display lists (Avoids referencing stale/recycled data and a crash on Nvidia)
* Route deletes through GLStateManager
2026-04-19 08:19:02 -07:00
Eclipse-Sol 4a2dcd0b22 Automate the collection of legacy IDs for very old shaderpacks. (#1631) 2026-04-18 09:53:06 -07:00
Eclipse-Sol 0db691db0b Add FlatteningMap and EntityFlatteningMap (#1635) 2026-04-18 09:20:24 -07:00
danyadev e5cd1372e7 always request 0.9.0+ GTNHLib version when a player has an outdated version (#1672) 2026-04-18 09:18:28 -07:00
Kotmatross fbd07cc575 NTM:Space compat rework and update (#1675) 2026-04-18 09:14:49 -07:00
George Paton 91f2cf9dfb Add "Mods Only" Dynamic FOV option (#1680) 2026-04-18 09:13:39 -07:00
Kaiz 34a6967c71 fix(iris): implement full GL alpha test (#1673) 2026-04-18 09:12:46 -07:00
Alexdoru 9c295a1e22 Fix cached item in RenderItemFrame leaking both world client and server (#1659) 2026-04-13 15:04:36 -07:00
DarkShadow44 9fd02900ef Load modern block properties (#1626) 2026-04-13 15:03:23 -07:00
Jason Mitchell 27e9a4d4fd Fix class circularity error with early redirector (#1670) 2026-04-13 15:02:26 -07:00
Darren Eberly dfc08053d5 Don't advance buffer positions for Material shininess in GLSM (#1668) 2026-04-12 22:25:25 -07:00
VortexSo4 1af03fa369 add CTM guide (#1667) 2026-04-11 17:49:38 -07:00
NealDeal34 3ae3b2527d Update zh_CN.lang (#1623)
* Update zh_CN.lang

* update zh_CN.lang for dynamic lights
2026-04-07 08:21:34 +02:00
Jason Mitchell fd2a14a000 Various fixes (#1652) 2026-04-06 17:35:15 -07:00
DarkShadow44 9f0f6a6232 Always render clouds late for shaders (#1649) 2026-04-06 09:52:34 -07:00
DarkShadow44 2942c7eb84 Disable clouds when shaders are active (#1648) 2026-04-06 09:52:21 -07:00
Alexdoru 47762f921e fix two world client memory leaks (#1650) 2026-04-06 16:38:03 +00:00
Jason Mitchell bdc4431760 Fix some issues someone was having with nixos (#1647) 2026-04-05 16:21:58 -07:00
DarkShadow44 7e1beadfb5 DH: Fix mismatch from upstream for solas shaders (#1645) 2026-04-04 17:27:19 -07:00
Jason Mitchell 59a4b84023 Standalone GLSM mod - Umbra (#1642) 2026-04-03 21:51:35 -07:00
Jason Mitchell 8d53e25e0a Compact CTM Fix (#1640) 2026-04-03 16:48:08 -07:00
VortexSo4 bbf83db379 Implement compact ctm (#1636) 2026-04-02 22:32:28 +00:00
Jason Mitchell ec82280b9b Match OpenGL Buffer Behavior for glGet (#1637) 2026-04-02 14:20:06 -07:00
DarkShadow44andJason Mitchell d7588e4c14 Adapt shader block id override for Angelica 2.x (#1601)
Co-authored-by: Jason Mitchell <[email protected]>
2026-04-02 14:18:55 -07:00
Jason Mitchell 54c99386d4 Do a similar fix as MixinDroppedItemGlintEdges to held items (#1632) 2026-03-31 18:31:21 -07:00
DarkShadow44 465abf889a Pass mc_Entity like upstream does (#1627) 2026-03-31 16:18:35 -07:00
DarkShadow44 4acb83be6a Port shadow translucent (aka shadow_water) from upstream (#1628) 2026-03-31 16:09:25 -07:00
DarkShadow44 80f9724ee6 Handle GL_QUAD_STRIP in legacy conversion (#1629) 2026-03-31 15:57:57 -07:00
Jason Mitchell 8cf55bb216 Safely handle off client thread crash reports/glGetString (#1630) 2026-03-31 15:49:47 -07:00
Eclipse-Sol 3ebc8868c7 Fix item reset hopefully for the last time (#1621) 2026-03-30 19:32:47 -07:00
JohnButlerThird 0938e6d975 Only run the logic on bop grass (fix my mod bugging out) (#1620) 2026-03-30 18:17:42 -07:00
Eclipse-Sol d3022a40d2 Create new deferred overlay system to fix z-fighting issues (#1605) 2026-03-30 15:50:18 -07:00
Eclipse-Sol 2645c803f9 Better horse support (#1599) 2026-03-30 15:41:53 -07:00
Jason Mitchell d91c356a99 Shader fixes (#1619) 2026-03-30 15:34:54 -07:00
Jason Mitchell 189ff4a80f Defer client attributes. They're uploaded/handled elsewhere. Should fix a crash on j8/lwjgl2 (#1618) 2026-03-30 09:58:40 -07:00
Jason Mitchell ffb4ca9472 Get rid of some allocations, and use immutable variants for the suppliers (#1612) 2026-03-30 09:58:27 -07:00
Jason Mitchell 7d8c8d306d More Double overloads (#1613) 2026-03-29 15:09:13 -07:00
Kotmatross 117ff0cacf Fix other dynamic uniforms not updating properly (#1610) 2026-03-29 12:53:09 -07:00
Jason Mitchell 83fdd57534 Narrow another overly broad transformer exclusion 2026-03-29 09:24:50 -07:00
Kaiz 951be04e9a fix: correctly handle cross-chunk TileEntity render bounds 2026-03-29 09:20:13 -07:00
Jason Mitchell 0ad69f3433 FINE, we'll work with java8/forge. 2026-03-28 20:09:42 -07:00
Fabian Maurer 0dabecc784 Handle GL_POLYGON in legacy conversion 2026-03-28 14:41:23 -07:00
Jason Mitchell 9b3fad4c6d Ensure sufficient coverage + optimize the check 2026-03-28 13:16:54 -07:00
Kaiz e1e8256861 fix: crash when client-side vertex attrib pointers are used in core profile 2026-03-28 13:16:54 -07:00
Jason Mitchell e65c01c2a3 Handle method handles in invokedynamic 2026-03-28 12:46:19 -07:00
Jason Mitchell b9de4df18e Update alpha test to hopefully work with RTM and AE2 (#1597) 2026-03-28 09:40:10 -07:00
Kotmatross a5d31cd1b6 Return phaseChangeNotifier (#1596) 2026-03-28 09:15:46 -07:00
DarkShadow44 29f8d718cc Pass texturemap to composite shader and honor texture replacements (#1589) 2026-03-27 19:07:45 -07:00
12411234 52b0db2566 Fix missing glGetTexImage method crash when using XaerosWorldMap (#1592) 2026-03-27 19:02:51 -07:00
Eclipse-Sol e9eb45f4f2 Centers the Wither Armor (#1590) 2026-03-27 18:37:17 -07:00
Jason Mitchell 92e28a4cd5 Blur Compat (#1593) 2026-03-27 18:32:24 -07:00
Jason Mitchell f46a86889c Fix crash with fancy block particles. (#1591) 2026-03-27 17:52:14 -07:00
Jason MitchellandAlexdoru c827d446d5 External Redirector API (#1580)
Co-authored-by: Alexdoru <[email protected]>
2026-03-27 11:20:29 -07:00
Eclipse-Sol 00ef355ce8 Fix sky lighting on drawCrossSquare rendered blocks. (#1576) 2026-03-25 18:58:40 -07:00
Kotmatross 1282056fff Fix blend leak in BatchingFontRenderer (#1575) 2026-03-25 15:34:43 -07:00
Alexdoru 6c422d47a2 Make the mod no-op on server (#1574) 2026-03-25 12:59:23 -07:00
DarkShadow44 057ea8035c Distant Horizons shader support (#1553) 2026-03-25 10:48:14 -07:00
1228 changed files with 93904 additions and 11420 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"
+64
View File
@@ -0,0 +1,64 @@
#!/usr/bin/env bash
# Builds the libTracyClient shared library for the tracy-client module.
set -euo pipefail
TRACY_VERSION="${TRACY_VERSION:-v0.13.1}"
OUT_DIR="${OUT_DIR:-dist/tracy-natives}"
TRACY_REPO="https://github.com/wolfpld/tracy"
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
case "$(uname -s)" in
Darwin) PLAT_OS="macos" ;;
Linux) PLAT_OS="linux" ;;
*) PLAT_OS="windows" ;;
esac
case "$(uname -m)" in
arm64|aarch64) PLAT_ARCH="arm64" ;;
*) PLAT_ARCH="x64" ;;
esac
RES_DIR="${RES_DIR:-$REPO_ROOT/tracy-client/src/main/resources/natives/tracy/$PLAT_OS-$PLAT_ARCH}"
WORK_DIR="$(mktemp -d)"
trap 'rm -rf "$WORK_DIR"' EXIT
if [[ -n "${TRACY_SRC:-}" ]]; then
SRC_DIR="$WORK_DIR/tracy"
git clone --shared --no-checkout "$TRACY_SRC" "$SRC_DIR"
git -C "$SRC_DIR" checkout --detach "$TRACY_VERSION"
else
SRC_DIR="$WORK_DIR/tracy"
git clone --depth 1 --branch "$TRACY_VERSION" "$TRACY_REPO" "$SRC_DIR"
fi
BUILD_DIR="$WORK_DIR/build"
export MACOSX_DEPLOYMENT_TARGET="${MACOSX_DEPLOYMENT_TARGET:-11.0}"
cmake -S "$SRC_DIR" -B "$BUILD_DIR" \
-DCMAKE_BUILD_TYPE=Release \
-DBUILD_SHARED_LIBS=ON \
-DTRACY_STATIC=OFF \
-DTRACY_ENABLE=ON \
-DTRACY_ON_DEMAND=ON \
-DTRACY_DELAYED_INIT=ON \
-DTRACY_MANUAL_LIFETIME=ON \
-DTRACY_NO_SAMPLING=ON \
-DTRACY_NO_SYSTEM_TRACING=ON \
-DTRACY_NO_CRASH_HANDLER=ON \
-DTRACY_LTO=OFF
cmake --build "$BUILD_DIR" --config Release --parallel
mkdir -p "$OUT_DIR"
for NAME in libTracyClient.so libTracyClient.dylib TracyClient.dll; do
LIB="$(find "$BUILD_DIR" -name "$NAME" | head -1)"
if [[ -n "$LIB" ]]; then
cp -L "$LIB" "$OUT_DIR/$NAME"
cp "$SRC_DIR/LICENSE" "$OUT_DIR/LICENSE"
mkdir -p "$RES_DIR"
cp -L "$LIB" "$RES_DIR/$NAME"
cp "$SRC_DIR/LICENSE" "$RES_DIR/LICENSE"
echo "built: $OUT_DIR/$NAME ($TRACY_VERSION), staged: $RES_DIR/$NAME"
exit 0
fi
done
echo "error: built library not found under $BUILD_DIR" >&2
exit 1
+92
View File
@@ -0,0 +1,92 @@
name: Build Tracy natives
on:
push:
tags:
- "tracy-v*"
workflow_dispatch:
inputs:
tracy-version:
description: Tracy git tag to build
required: false
default: v0.13.1
publish:
description: Publish angelica-tracy.jar to the GTNH maven
type: boolean
required: false
default: false
jobs:
build:
strategy:
matrix:
include:
- os: ubuntu-22.04
platform: linux-x64
- os: windows-2025
platform: windows-x64
- os: macos-15-intel
platform: macos-x64
- os: macos-15
platform: macos-arm64
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v5
- name: Build libTracyClient
shell: bash
env:
RAW_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tracy-version }}
OUT_DIR: dist/tracy-natives
run: |
export TRACY_VERSION="${RAW_VERSION#tracy-}"
bash .github/scripts/build-tracy-natives.sh
- uses: actions/upload-artifact@v4
with:
name: tracy-natives-${{ matrix.platform }}
path: dist/tracy-natives/*
if-no-files-found: error
assemble:
needs: build
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/setup-java@v5
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v4
- name: Check tracy version matches the version catalog
shell: bash
env:
RAW_VERSION: ${{ github.ref_type == 'tag' && github.ref_name || inputs.tracy-version }}
run: |
ver="${RAW_VERSION#tracy-}"
grep -q "^tracy = \"${ver#v}\"$" gradle/libs.versions.toml \
|| { echo "libs.versions.toml tracy does not match $ver" >&2; exit 1; }
- uses: actions/download-artifact@v5
with:
pattern: tracy-natives-*
path: staging
- name: Stage natives into tracy-client resources
shell: bash
run: |
for d in staging/tracy-natives-*; do
dest="tracy-client/src/main/resources/natives/tracy/${d#staging/tracy-natives-}"
mkdir -p "$dest"
cp "$d"/* "$dest/"
done
find tracy-client/src/main/resources/natives -type f
- name: Build angelica-tracy.jar
run: ./gradlew :tracy-client:distJar
- uses: actions/upload-artifact@v4
with:
name: angelica-tracy
path: tracy-client/build/libs/angelica-tracy-*.jar
if-no-files-found: error
- name: Publish to GTNH maven
if: github.ref_type == 'tag' || inputs.publish
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
run: ./gradlew -PpublishTracy=true :tracy-client:publish
+20 -1
View File
@@ -3,7 +3,7 @@ name: Release tagged build
on:
push:
tags: [ '*' ]
tags: [ '*', '!tracy-v*' ]
permissions:
contents: write
@@ -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
+6 -1
View File
@@ -1,9 +1,10 @@
.gradle
.kotlin/
.settings
/.idea/
/.vs/
/.vscode/
/run/
run/
build/
/eclipse/
.classpath
@@ -12,6 +13,7 @@ build/
/config/
/crash-reports/
/logs/
tmp/
options.txt
/saves/
usernamecache.json
@@ -37,3 +39,6 @@ addon.local.gradle.kts
addon.late.local.gradle
addon.late.local.gradle.kts
local/
tracy-client/src/main/resources/natives/
dist/tracy-natives/
*.tracy
+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
+17
View File
@@ -0,0 +1,17 @@
# Angelica dev commands
# Build with headless display
build:
xvfb-run --server-args="-screen 0 1366x768x24" ./gradlew build
# Run tests with headless display
test:
xvfb-run --server-args="-screen 0 1366x768x24" ./gradlew test
# Build jar only (no tests)
jar:
./gradlew jar
# Clean build directory
clean:
./gradlew clean
+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
+195 -28
View File
@@ -1,35 +1,129 @@
import com.modrinth.minotaur.TaskModrinthUpload
import net.darkhax.curseforgegradle.TaskPublishCurseForge
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")
}
minecraft {
extraRunJvmArguments.add("-Dangelica.enableTestBlocks=true")
extraRunJvmArguments.add("-Dangelica.dumpClass=true")
// extraRunJvmArguments.add("-Dangelica.useVulkan=true")
// extraRunJvmArguments.add("-Dangelica.vulkanDebug=true")
// extraRunJvmArguments.add("-Dorg.lwjgl.util.Debug=true")
tasks.withType<DowngradeJar>().configureEach { logLevel.set("FATAL") }
tasks.withType<DowngradeFiles>().configureEach { logLevel.set("FATAL") }
val lwjglDebug = false
val gpuHud = false
val renderdoc = false
val rgp = false
minecraft {
extraRunJvmArguments.add("-Dangelica.debug.testBlocks=true")
extraRunJvmArguments.add("-Dangelica.tracy=true")
extraRunJvmArguments.add("-Dangelica.sdlgpu.enable=true")
extraRunJvmArguments.add("-Dangelica.unmappedGL=fail")
extraRunJvmArguments.add("-Dorg.lwjgl.util.Debug=$lwjglDebug")
extraRunJvmArguments.add("-Dsun.net.client.defaultConnectTimeout=5000")
extraRunJvmArguments.add("-Dsun.net.client.defaultReadTimeout=5000")
// TODO: Remove once GTNHGradle includes shaderc/spvc in the default lwjgl3Bindings list
lwjgl3Bindings.addAll("shaderc", "spvc")
lwjgl3Version = libs.versions.lwjgl3.get()
// extraRunJvmArguments.addAll("-Dlegacy.debugClassLoadingSave=true")
// extraRunJvmArguments.addAll("-Drfb.dumpLoadedClasses=true", "-Drfb.dumpLoadedClassesPerTransformer=true")
//extraRunJvmArguments.add("-Dangelica.redirectorLogspam=true")
//extraRunJvmArguments.add("-Dangelica.debug.redirectorLogspam=true")
}
tasks.test {
useJUnitPlatform()
testLogging {
events("passed", "skipped", "failed")
}
// On macOS ARM64, use an ARM64-compatible JVM for tests
val isMacOsArm64 = System.getProperty("os.name").lowercase().contains("mac") && System.getProperty("os.arch") == "aarch64"
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(8)
if (isMacOsArm64) {
vendor = JvmVendorSpec.AZUL
// Forward -Dangelica.* from the gradle invocation to the client
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
for ((key, value) in System.getProperties()) {
val name = key.toString()
if (name.startsWith("angelica.")) {
jvmArgs("-D$name=$value")
}
}
}
if (gpuHud) {
tasks.withType<JavaExec>().matching { it.name.startsWith("runClient") }.configureEach {
if (org.gradle.internal.os.OperatingSystem.current().isMacOsX) {
environment("MTL_HUD_ENABLED", "1")
} else {
environment("MANGOHUD", "1")
environment("LD_PRELOAD", "/usr/\$LIB/mangohud/libMangoHud_shim.so")
}
}
}
configurations.all {
exclude(group = "com.github.GTNewHorizons", module = "Angelica")
resolutionStrategy.dependencySubstitution {
substitute(module("com.github.GTNewHorizons:lwjgl3ify"))
.using(module("com.github.GTNewHorizons:lwjgl3ify:${libs.versions.lwjgl3ify.get()}"))
}
}
// lwjgl3ify pulls a pinned lwjgl 3.4.2 snapshot transitively from here.
repositories {
maven {
name = "Maven Central Snapshots"
url = uri("https://central.sonatype.com/repository/maven-snapshots/")
content { includeGroup("org.lwjgl") }
}
}
val osName = System.getProperty("os.name").lowercase()
val isMacOs = org.gradle.internal.os.OperatingSystem.current().isMacOsX
val isMacOsArm64 = isMacOs && System.getProperty("os.arch") == "aarch64"
tasks.withType<JavaExec>().configureEach {
if (name.startsWith("runClient") && name != "runClient") {
if (isMacOs) {
// SDL3 / Cocoa / Metal must initialize on the JVM main thread.
jvmArgs("-XstartOnFirstThread")
jvmArgs("-Dangelica.sdlgpu.encoderAssertions=" + if (lwjglDebug) "fatal" else "warn")
if (lwjglDebug) {
environment("METAL_DEVICE_WRAPPER_TYPE", "1")
environment("METAL_DEBUG_ERROR_MODE", "0")
environment("MTL_SHADER_VALIDATION", "1")
environment("MTL_SHADER_VALIDATION_REPORT_TO_STDERR", "1")
environment("MTL_DEBUG_LAYER", "1")
environment("MallocScribble", "1")
environment("MallocPreScribble", "1")
environment("MallocGuardEdges", "1")
environment("MallocStackLogging", "1")
environment("MallocStackLoggingNoCompact", "1")
}
}
}
}
// Linux runClient: RenderDoc frame capture (F12) OR RADV RGP/SQTT capture. Mutually exclusive; RGP wins
tasks.withType<JavaExec>().configureEach {
if (name.startsWith("runClient") && !isMacOs && !osName.contains("windows")) {
environment("WAYLAND_DISPLAY", "")
environment("XDG_SESSION_TYPE", "x11")
val rgpFrame = project.findProperty("rgpFrame") as String?
val rgpCapture = rgp || project.hasProperty("rgp") || rgpFrame != null
if (rgpCapture) {
environment("MESA_VK_TRACE", "rgp")
if (rgpFrame != null) {
environment("MESA_VK_TRACE_FRAME", rgpFrame)
doFirst { logger.lifecycle("RGP: capturing frame $rgpFrame -> /tmp/java_*.rgp") }
} else {
environment("MESA_VK_TRACE_TRIGGER", "/tmp/angelica_rgp.trigger")
doFirst { logger.lifecycle("RGP: touch /tmp/angelica_rgp.trigger (or press F1) to capture -> /tmp/java_*.rgp") }
}
} else if (renderdoc || project.hasProperty("renderdoc")) {
val renderdocLib = file(project.findProperty("renderdocLib") as String? ?: "${System.getProperty("user.home")}/Downloads/renderdoc_1.44/lib/librenderdoc.so")
if (renderdocLib.isFile) {
environment("LD_PRELOAD", renderdocLib.absolutePath)
doFirst { logger.lifecycle("RENDERDOC: LD_PRELOAD set, press F12 to capture") }
} else {
doFirst { logger.lifecycle("RENDERDOC: enabled but ${renderdocLib.absolutePath} not found; set -PrenderdocLib=<path>") }
}
}
}
dependsOn(tasks.extractNatives2)
jvmArgs("-Djava.library.path=${tasks.extractNatives2.get().destinationFolder.asFile.get().path}")
}
tasks.processResources {
@@ -52,6 +146,7 @@ val shadowImplementation: Configuration by configurations
val downgradeEmbedOnlyForTest by tasks.registering(DowngradeFiles::class) {
inputCollection = embedOnly
outputs.dir(temporaryDir)
}
val stripModuleInfoFromShadow by tasks.registering(Jar::class) {
@@ -59,7 +154,7 @@ val stripModuleInfoFromShadow by tasks.registering(Jar::class) {
from(shadowImplementation.map(::zipTree))
exclude("module-info.class")
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
archiveClassifier = "shadow-stripped"
archiveFileName = "shadow-stripped.jar"
}
val downgradeShadowImplForTest by tasks.registering(DowngradeFiles::class) {
@@ -67,15 +162,74 @@ val downgradeShadowImplForTest by tasks.registering(DowngradeFiles::class) {
outputs.dir(temporaryDir)
}
tasks.test {
dependsOn(downgradeEmbedOnlyForTest, downgradeShadowImplForTest)
classpath = classpath
.minus(embedOnly)
.plus(files(downgradeEmbedOnlyForTest.map { it.outputCollection }))
.minus(shadowImplementation)
.plus(files({ fileTree(downgradeShadowImplForTest.get().temporaryDir) }))
val glsmTestFixtures: Configuration by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
attributes {
attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME))
attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR))
}
}
val downgradeGlsmTestFixturesForTest by tasks.registering(DowngradeFiles::class) {
inputCollection = glsmTestFixtures
outputs.dir(temporaryDir)
}
fun downgraded(task: TaskProvider<DowngradeFiles>): FileCollection = files(task.map { it.outputMap.values })
fun Test.configureAngelicaJava8() {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(8)
if (isMacOsArm64) {
vendor = JvmVendorSpec.AZUL
}
}
dependsOn(tasks.extractNatives2)
jvmArgs("-Djava.library.path=${tasks.extractNatives2.get().destinationFolder.asFile.get().path}")
testLogging { events("passed", "skipped", "failed") }
val swaps = mapOf(
embedOnly to downgradeEmbedOnlyForTest,
shadowImplementation to downgradeShadowImplForTest,
glsmTestFixtures to downgradeGlsmTestFixturesForTest,
)
dependsOn(swaps.values)
for ((original, downgrade) in swaps) {
classpath = classpath.minus(original).plus(downgraded(downgrade))
}
}
tasks.test {
useJUnitPlatform { excludeTags = setOf("gl-core") }
configureAngelicaJava8()
dependsOn(":glsm:classes", ":lwjgl3-backend:classes", ":sdl-gpu:classes")
}
val downgradeRootMainClasses = tasks.named<DowngradeFiles>("downgradeMainClasses")
val downgradeRootTestClasses = tasks.named<DowngradeFiles>("downgradeTestClasses")
val glCoreTest by tasks.registering(Test::class) {
description = "Runs the gl-core tests in their own JVM with a real GL context."
group = "verification"
useJUnitPlatform { includeTags = setOf("gl-core") }
dependsOn(downgradeRootMainClasses, downgradeRootTestClasses)
val rawMain = sourceSets["main"].output.classesDirs
val rawTest = sourceSets["test"].output.classesDirs
val downgradedTest = downgraded(downgradeRootTestClasses)
setTestClassesDirs(downgradedTest)
classpath = downgradedTest
.plus(downgraded(downgradeRootMainClasses))
.plus(sourceSets["test"].runtimeClasspath.minus(rawMain).minus(rawTest))
configureAngelicaJava8()
mustRunAfter(tasks.test)
}
tasks.test { finalizedBy(glCoreTest) }
tasks.shadowJar {
dependsOn(embedOnly)
from(embedOnly.map(::zipTree))
@@ -84,13 +238,26 @@ tasks.shadowJar {
mergeServiceFiles()
minimize {
exclude(project(rootProject.path))
exclude(project(":glsm"))
exclude(project(":lwjgl3-backend"))
exclude(project(":sdl-gpu"))
exclude(dependency("org.taumc:.*:.*"))
exclude(dependency("org.antlr:.*:.*"))
}
relocate("com.mitchej123", "com.mitchej123")
relocate("org.embeddedt", "org.embeddedt")
relocate("com.gtnewhorizons.angelica.config", "com.gtnewhorizons.angelica.config")
relocate("com.gtnewhorizons.angelica.glsm", "com.gtnewhorizons.angelica.glsm")
relocate("com.gtnewhorizons.angelica.lwjgl3", "com.gtnewhorizons.angelica.lwjgl3")
relocate("com.gtnewhorizons.angelica.sdlgpu", "com.gtnewhorizons.angelica.sdlgpu")
}
tasks.withType<TaskPublishCurseForge>().configureEach {
uploadArtifacts.forEach { it.addGameVersion("Client") }
}
tasks.withType<TaskModrinthUpload>().configureEach {
setDependsOn(dependsOn.filterNot { it == "build" } + "assemble")
}
+14
View File
@@ -0,0 +1,14 @@
plugins {
`kotlin-dsl`
}
repositories {
gradlePluginPortal()
mavenCentral()
}
// Re-export Gradle's generated typed catalog accessors so applied scripts (dependencies.gradle.kts) can use them.
dependencies {
implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location))
implementation(libs.asm.build)
}
+5
View File
@@ -0,0 +1,5 @@
dependencyResolutionManagement {
versionCatalogs {
create("libs") { from(files("../gradle/libs.versions.toml")) }
}
}
@@ -0,0 +1,49 @@
import org.objectweb.asm.AnnotationVisitor
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.Opcodes
import java.io.File
private const val LWJGL3_AWARE_DESC = "Lme/eigenraven/lwjgl3ify/api/Lwjgl3Aware;"
/**
* Stamps @Lwjgl3Aware onto every class under [classesDir] - classes that already carry the annotation are left untouched.
*/
fun injectLwjgl3Aware(classesDir: File) {
if (!classesDir.isDirectory) return
classesDir.walkTopDown()
.filter { it.isFile && it.extension == "class" && it.name != "module-info.class" }
.forEach { classFile ->
val reader = ClassReader(classFile.readBytes())
if (hasLwjgl3Aware(reader)) return@forEach
val writer = ClassWriter(reader, 0)
reader.accept(object : ClassVisitor(Opcodes.ASM9, writer) {
override fun visit(
version: Int,
access: Int,
name: String?,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
super.visit(version, access, name, signature, superName, interfaces)
super.visitAnnotation(LWJGL3_AWARE_DESC, true)?.visitEnd()
}
}, 0)
classFile.writeBytes(writer.toByteArray())
}
}
private fun hasLwjgl3Aware(reader: ClassReader): Boolean {
var found = false
reader.accept(object : ClassVisitor(Opcodes.ASM9) {
override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? {
if (descriptor == LWJGL3_AWARE_DESC) found = true
return null
}
}, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG or ClassReader.SKIP_FRAMES)
return found
}
-155
View File
@@ -1,155 +0,0 @@
/*
* Add your dependencies here. Supported configurations:
* - api("group:name:version:classifier"): if you use the types from this dependency in the public API of this mod
* Available at runtime and compiletime for mods depending on this mod
* - implementation("g:n:v:c"): if you need this for internal implementation details of the mod, but none of it is visible via the public API
* Available at runtime but not compiletime for mods depending on this mod
* - compileOnly("g:n:v:c"): if the mod you're building doesn't need this dependency during runtime at all, e.g. for optional mods
* Not available at all for mods depending on this mod, only visible at compiletime for this mod
* - compileOnlyApi("g:n:v:c"): like compileOnly, but also visible at compiletime for mods depending on this mod
* Available at compiletime but not runtime for mods depending on this mod
* - runtimeOnlyNonPublishable("g:n:v:c"): if you want to include a mod in this mod's runClient/runServer runs, but not publish it as a dependency
* Not available at all for mods depending on this mod, only visible at runtime for this mod
* - devOnlyNonPublishable("g:n:v:c"): a combination of runtimeOnlyNonPublishable and compileOnly for dependencies present at both compiletime and runtime,
* but not published as Maven dependencies - useful for RFG-deobfuscated dependencies or local testing
* - runtimeOnly("g:n:v:c"): if you don't need this at compile time, but want it to be present at runtime
* Available at runtime for mods depending on this mod
* - annotationProcessor("g:n:v:c"): mostly for java compiler plugins, if you know you need this, use it, otherwise don't worry
* - testCONFIG("g:n:v:c") - replace CONFIG by one of the above (except api), same as above but for the test sources instead of main
*
* - shadowImplementation("g:n:v:c"): effectively the same as API, but the dependency is included in your jar under a renamed package name
* Requires you to enable usesShadowedDependencies in gradle.properties
*
* - compile("g:n:v:c"): deprecated, replace with "api" (works like the old "compile") or "implementation" (can be more efficient)
*
* You can exclude transitive dependencies (dependencies of the chosen dependency) by appending { transitive = false } if needed,
* but use this sparingly as it can break using your mod as another mod's dependency if you're not careful.
*
* To depend on obfuscated jars you can use `devOnlyNonPublishable(rfg.deobf("dep:spec:1.2.3"))` to fetch an obfuscated jar from maven,
* or `devOnlyNonPublishable(rfg.deobf(project.files("libs/my-mod-jar.jar")))` to use a file.
*
* Gradle names for some of the configuration can be misleading, compileOnlyApi and runtimeOnly both get published as dependencies in Maven, but compileOnly does not.
* The buildscript adds runtimeOnlyNonPublishable to also have a runtime dependency that's not published.
*
* For more details, see https://docs.gradle.org/8.0.1/userguide/java_library_plugin.html#sec:java_library_configurations_graph
*/
configurations {
transformedMod // Mods that can be transformed - used for compiling angelica, but not necessary at runtime
transformedMod.canBeConsumed = false
transformedModCompileOnly
transformedModCompileOnly.canBeConsumed = false
// Add the transformed mod dependencies to the compilation classpaths, but don't publish them in the Maven metadata
compileClasspath.extendsFrom(transformedMod, transformedModCompileOnly)
testCompileClasspath.extendsFrom(transformedMod, transformedModCompileOnly)
// Can be enabled for testing if desired
// runtimeClasspath.extendsFrom(transformedMod)
// testRuntimeClasspath.extendsFrom(transformedMod)
testRuntimeClasspath.exclude group: "com.github.GTNewHorizons", module: "lwjgl3ify"
// Embed in JAR without relocation (compile only, shadowed into JAR)
embedOnly
embedOnly.canBeConsumed = false
embedOnly.canBeResolved = true
compileClasspath.extendsFrom(embedOnly)
testCompileClasspath.extendsFrom(embedOnly)
testRuntimeClasspath.extendsFrom(embedOnly)
}
// Exclude self-reference from all configurations - prevents transitive Angelica deps from resolving against this project
configurations.all {
exclude group: "com.github.GTNewHorizons", module: "Angelica"
}
dependencies {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.14:dev")
compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.7") { transitive = false }
// runtimeOnlyNonPublishable("com.github.GTNewHorizons:lwjgl3ify:3.0.99")
compileOnly("org.projectlombok:lombok:1.18.42") { transitive = false }
annotationProcessor("org.projectlombok:lombok:1.18.42")
// Iris Shaders
compileOnly('org.jetbrains:annotations:26.0.2')
api("com.github.GTNewHorizons:GTNHLib:0.9.36: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-26.g5ee7d96-GTNH") {
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-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')
// Notfine Deps
compileOnly("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles-Expanded:2.2.2-GTNH:dev")
compileOnly("com.github.GTNewHorizons:twilightforest:2.7.13:dev") { transitive = false }
compileOnly(rfg.deobf('curse.maven:witchery-69673:2234410'))
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.14.6-GTNH:dev") { transitive = false }
compileOnly("com.github.GTNewHorizons:Natura:2.8.9:dev")
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.8.4:dev")
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.3:dev") { transitive = false }
compileOnly("com.falsepattern:chunkapi-mc1.7.10:0.5.1:api") { transitive = false }
compileOnly("com.falsepattern:endlessids-mc1.7.10:1.5-beta0003:dev") { transitive = false }
compileOnly(rfg.deobf("curse.maven:extrautils-225561:2264383"))
compileOnly(rfg.deobf("curse.maven:dynamiclights-227874:2337326"))
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.10:dev")
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5593_H261"))
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(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
// For testing alternative splash screens — https://github.com/MalTeeez/ModernSplash/releases
// devOnlyNonPublishable("com.github.MalTeeez:modernsplash:1.7.10-1.2.4:dev")
// Hodgepodge
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
// Better Crashes
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.1-GTNH:dev")
afterEvaluate {
dependencies {
testImplementation(platform('org.junit:junit-bom:5.14.1'))
testImplementation('org.junit.jupiter:junit-jupiter')
testImplementation("org.mockito:mockito-core:3.+")
testImplementation("org.lwjgl.lwjgl:lwjgl:2.9.4-nightly-20150209")
testImplementation('org.junit.platform:junit-platform-engine')
testImplementation('org.junit.platform:junit-platform-reporting')
testImplementation('org.junit.platform:junit-platform-launcher')
testImplementation('com.tngtech.archunit:archunit-junit5:1.4.1')
}
}
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
}
+209
View File
@@ -0,0 +1,209 @@
/*
* Angelica's dependency declarations. Supported configurations:
* - api("group:name:version:classifier"): if you use the types from this dependency in the public API of this mod
* Available at runtime and compiletime for mods depending on this mod
* - implementation("g:n:v:c"): if you need this for internal implementation details of the mod, but none of it is visible via the public API
* Available at runtime but not compiletime for mods depending on this mod
* - compileOnly("g:n:v:c"): if the mod you're building doesn't need this dependency during runtime at all, e.g. for optional mods
* Not available at all for mods depending on this mod, only visible at compiletime for this mod
* - runtimeOnlyNonPublishable("g:n:v:c"): if you want to include a mod in this mod's runClient/runServer runs, but not publish it as a dependency
* - devOnlyNonPublishable("g:n:v:c"): a combination of runtimeOnlyNonPublishable and compileOnly, not published as a Maven dependency
* - shadowImplementation("g:n:v:c"): like api, but included in your jar under a renamed package name
* - embedOnly("g:n:v:c"): embedded in the jar without relocation
*
* Versions come from gradle/libs.versions.toml. Mod coordinates stay literal: nearly all carry a
* :dev classifier, which a version catalog cannot express, and curse/modrinth "versions" are file ids.
*
* To depend on obfuscated jars use `rfg.deobf("dep:spec:1.2.3")`.
*/
import com.gtnewhorizons.retrofuturagradle.modutils.ModUtils
import org.gradle.accessors.dm.LibrariesForLibs
import org.gradle.kotlin.dsl.DependencyHandlerScope
val libs = the<LibrariesForLibs>()
val rfg = (dependencies as ExtensionAware).extensions.getByType<ModUtils.RfgDependencyExtension>()
val api = configurations["api"]
val compileOnly = configurations["compileOnly"]
val annotationProcessor = configurations["annotationProcessor"]
val shadowImplementation = configurations["shadowImplementation"]
val runtimeOnlyNonPublishable = configurations["runtimeOnlyNonPublishable"]
val devOnlyNonPublishable = configurations["devOnlyNonPublishable"]
// Mods that can be transformed - used for compiling angelica, but not necessary at runtime
val transformedMod by configurations.creating { isCanBeConsumed = false }
val transformedModCompileOnly by configurations.creating { isCanBeConsumed = false }
// Embed in JAR without relocation (compile only, shadowed into JAR)
val embedOnly by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
// Add the transformed mod dependencies to the compilation classpaths, but don't publish them in the Maven metadata
configurations["compileClasspath"].extendsFrom(transformedMod, transformedModCompileOnly, embedOnly)
configurations["testCompileClasspath"].extendsFrom(transformedMod, transformedModCompileOnly, embedOnly)
configurations["testRuntimeClasspath"].extendsFrom(embedOnly)
configurations["testRuntimeClasspath"].exclude(group = "com.github.GTNewHorizons", module = "lwjgl3ify")
// Prevents transitive Angelica deps from resolving against this project
configurations.configureEach {
exclude(group = "com.github.GTNewHorizons", module = "Angelica")
}
DependencyHandlerScope.of(dependencies).apply {
compileOnly("com.github.GTNewHorizons:Hodgepodge:2.7.166:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Hodgepodge:2.7.166:dev")
runtimeOnlyNonPublishable(rfg.deobf("curse.maven:biomes-o-plenty-220318:2499612"))
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-beta-994-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ae2stuff:0.10.19-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BetterLoadingScreen:1.7.9-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BetterQuesting:3.8.72-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BuildCraft:7.1.63:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Botania:1.13.32-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:LogisticsPipes:1.5.32-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenComputers:1.12.55-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Mobs-Info:0.5.18-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:IguanaTweaksTConstruct:2.7.10:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenBlocks:1.12.18-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Schematica:1.12.6-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:TinkersConstruct:1.14.93-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ServerUtilities:2.4.1:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ironchest:6.1.13:dev")
runtimeOnlyNonPublishable("ganymedes01.etfuturum:Et-Futurum-Requiem:2.6.49-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:waila:1.19.30:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Draconic-Evolution:1.5.30-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:HoloInventory:2.5.15-GTNH:dev")
runtimeOnlyNonPublishable("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
compileOnly(libs.retrofuturabootstrap) { isTransitive = false }
// runtimeOnlyNonPublishable(libs.lwjgl3ify)
compileOnly(libs.lombok) { isTransitive = false }
annotationProcessor(libs.lombok)
// Iris Shaders
compileOnly(libs.annotations)
api(libs.gtnhlib) { artifact { classifier = "dev" } }
shadowImplementation(project(":glsm")) { isTransitive = false }
shadowImplementation(project(":lwjgl3-backend")) { isTransitive = false }
shadowImplementation(project(":sdl-gpu")) { isTransitive = false }
runtimeOnlyNonPublishable(project(":tracy-client")) { isTransitive = false }
shadowImplementation(libs.eventbus)
shadowImplementation(libs.jcpp) // Apache 2.0
shadowImplementation(libs.glsl.transformation.lib) {
exclude(module = "antlr4") // we only want to shadow the runtime module
}
shadowImplementation(libs.antlr4.runtime)
compileOnly(libs.ant)
// Celeritas (isTransitive=false: deps provided by GTNHLib/runtime)
embedOnly(libs.celeritas.common) { isTransitive = false }
embedOnly(libs.celeritas.lwjgl2.service) { isTransitive = false }
// Because who doesn't want NEI
devOnlyNonPublishable("com.github.GTNewHorizons:NotEnoughItems:2.8.109-GTNH:dev")
devOnlyNonPublishable("com.github.GTNewHorizons:CodeChickenCore:1.4.16:dev")
// BQ Testing
// devOnlyNonPublishable("com.github.GTNewHorizons:BetterQuesting:3.8.74-GTNH:dev")
// Display List Testing
/*
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Applied-Energistics-2-Unofficial:rv3-beta-994-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ae2stuff:0.10.19-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:BuildCraft:7.1.61:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:IguanaTweaksTConstruct:2.7.10:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenBlocks:1.12.18-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:OpenComputers:1.12.47-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:TinkersConstruct:1.14.93-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ServerUtilities:2.4.1:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:ironchest:6.1.13:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:waila:1.19.30:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Botania:1.13.25-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Mobs-Info:0.5.18-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:Draconic-Evolution:1.5.27-GTNH:dev")
runtimeOnlyNonPublishable("com.github.GTNewHorizons:HoloInventory:2.5.15-GTNH:dev")
*/
//devOnlyNonPublishable("com.github.GTNewHorizons:worldedit-gtnh:v0.0.9")
// Notfine Deps
compileOnly("thaumcraft:Thaumcraft:1.7.10-4.2.3.5:dev")
devOnlyNonPublishable("com.github.GTNewHorizons:Baubles-Expanded:2.2.21-GTNH:dev")
compileOnly("com.github.GTNewHorizons:twilightforest:2.7.36:dev") { isTransitive = false }
compileOnly(rfg.deobf("curse.maven:witchery-69673:2234410"))
compileOnly("com.github.GTNewHorizons:TinkersConstruct:1.14.93-GTNH:dev") { isTransitive = false }
compileOnly("com.github.GTNewHorizons:Natura:2.8.19:dev")
compileOnly("com.github.GTNewHorizons:ThaumicHorizons:1.8.20:dev")
compileOnly("com.github.GTNewHorizons:Battlegear2:1.4.3:dev") { isTransitive = false }
compileOnly("com.falsepattern:chunkapi-mc1.7.10:0.5.1:api") { isTransitive = false }
compileOnly("com.falsepattern:endlessids-mc1.7.10:1.5-beta0003:dev") { isTransitive = false }
compileOnly(rfg.deobf("curse.maven:extrautils-225561:2264383"))
compileOnly(rfg.deobf("curse.maven:dynamiclights-227874:2337326"))
compileOnly("curse.maven:minefactory-reloaded-66672:2277486")
compileOnly("com.github.GTNewHorizons:NotEnoughIds:2.1.11:dev")
compileOnly(rfg.deobf("maven.modrinth:ntmspace:X5663_H261"))
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("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.6-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")
// Hodgepodge
transformedMod("net.industrial-craft:industrialcraft-2:2.2.828-experimental:dev")
// Better Crashes
compileOnly("com.github.GTNewHorizons:BetterCrashes:1.4.5-GTNH:dev")
// Distant Horizons
compileOnly("maven.modrinth:DistantHorizonsApi:5.1.0")
compileOnly("mega:fluidlogged-mc1.7.10:0.1.2")
compileOnly("com.cardinalstar.cubicchunks:CubicChunks1710:v0.1.5-alpha:dev") { isTransitive = false }
}
afterEvaluate {
val glsmTestFixtures = configurations["glsmTestFixtures"]
val testImplementation = configurations["testImplementation"]
val testCompileOnly = configurations["testCompileOnly"]
val testRuntimeOnly = configurations["testRuntimeOnly"]
DependencyHandlerScope.of(dependencies).apply {
testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter)
testImplementation(libs.mockito.core)
testImplementation(libs.lwjgl2)
testImplementation(libs.junit.platform.engine)
testImplementation(libs.junit.platform.reporting)
testImplementation(libs.junit.platform.launcher)
testImplementation(libs.archunit.junit5)
glsmTestFixtures(testFixtures(project(":glsm")) as ModuleDependency) { isTransitive = false }
testCompileOnly(testFixtures(project(":glsm")))
testRuntimeOnly(libs.fastutil)
testRuntimeOnly(libs.joml)
}
}
+170 -31
View File
@@ -2,6 +2,7 @@ import xyz.wagyourtail.jvmdg.gradle.task.files.DowngradeFiles
plugins {
`java-library`
`java-test-fixtures`
`maven-publish`
}
@@ -9,6 +10,8 @@ apply(plugin = "xyz.wagyourtail.jvmdowngrader")
version = rootProject.version
val lwjglNatives: String by extra
java {
toolchain.languageVersion.set(JavaLanguageVersion.of(21))
withSourcesJar()
@@ -20,7 +23,7 @@ val rfgObfAttr = Attribute.of("com.gtnewhorizons.retrofuturagradle.obfuscation",
val rfgTransformedAttr = Attribute.of("rfgDeobfuscatorTransformed", Boolean::class.javaObjectType)
configurations {
listOf("apiElements", "runtimeElements").forEach { name ->
listOf("apiElements", "runtimeElements", "testFixturesApiElements", "testFixturesRuntimeElements").forEach { name ->
named(name) {
attributes {
attribute(rfgObfAttr, "mcp")
@@ -31,7 +34,7 @@ configurations {
}
// Compile stubs: MC class signatures needed to resolve GTNHLib's type hierarchy.
// Not included in the published jar — real MC classes are on the classpath at runtime.
// Not included in the published jar - real MC classes are on the classpath at runtime.
sourceSets {
create("stubs") {
java.srcDir("src/stubs/java")
@@ -65,7 +68,18 @@ repositories {
url = uri("https://maven.wagyourtail.xyz/releases")
content { includeGroup("xyz.wagyourtail.jvmdowngrader") }
}
maven {
name = "Sonatype Snapshots"
url = uri("https://oss.sonatype.org/content/repositories/snapshots/")
}
maven {
name = "Maven Central Snapshots"
url = uri("https://central.sonatype.com/repository/maven-snapshots/")
}
mavenLocal()
mavenCentral()
mavenLocal()
}
dependencies {
@@ -73,39 +87,62 @@ dependencies {
compileOnly(sourceSets["stubs"].output)
// LWJGL
compileOnly("org.lwjgl.lwjgl:lwjgl:${property("lwjglVersion")}")
compileOnly("org.lwjgl.lwjgl:lwjgl_util:${property("lwjglVersion")}")
compileOnly(libs.lwjgl2)
compileOnly(libs.lwjgl2.util)
compileOnly("org.ow2.asm:asm-tree:9.7")
compileOnly(libs.asm.tree.compile)
// Our Deps
api("com.github.GTNewHorizons:GTNHLib:${property("gtnhlibVersion")}:dev")
api("net.minecraftforge:eventbus:${property("eventbusVersion")}")
api(libs.gtnhlib) { artifact { classifier = "dev" } }
api(libs.eventbus)
compileOnly("org.embeddedt.celeritas:celeritas-common:${property("celeritasVersion")}") { isTransitive = false }
implementation("org.taumc:glsl-transformation-lib:${property("glslTransformLibVersion")}") { exclude(module = "antlr4") }
implementation("org.antlr:antlr4-runtime:${property("antlr4RuntimeVersion")}")
implementation("org.anarres:jcpp:${property("jcppVersion")}")
compileOnly(libs.celeritas.common) { isTransitive = false }
api(libs.glsl.transformation.lib) { exclude(module = "antlr4") }
api(libs.antlr4.runtime)
implementation(libs.jcpp)
compileOnly("org.projectlombok:lombok:${property("lombokVersion")}") { isTransitive = false }
annotationProcessor("org.projectlombok:lombok:${property("lombokVersion")}")
compileOnly("org.jetbrains:annotations:${property("jetbrainsAnnotationsVersion")}")
compileOnly("org.apache.logging.log4j:log4j-api:${property("log4jVersion")}")
// shaderc + SPIRV-Cross for the SpirvShaderTranslator (GLSL -> SPIR-V -> GLSL ES).
// TODO: Remove
compileOnly(libs.lwjgl3.shaderc)
compileOnly(libs.lwjgl3.spvc)
testImplementation(libs.lwjgl3.shaderc)
testImplementation(libs.lwjgl3.spvc)
// Host natives so the JNI wrapper can find libshaderc / libspirv-cross / liblwjgl.
testRuntimeOnly(libs.lwjgl3.shaderc) { artifact { classifier = lwjglNatives } }
testRuntimeOnly(libs.lwjgl3.spvc) { artifact { classifier = lwjglNatives } }
testRuntimeOnly(libs.lwjgl3)
testRuntimeOnly(libs.lwjgl3) { artifact { classifier = lwjglNatives } }
// @Lwjgl3Aware annotation
compileOnly(libs.lwjgl3ify) { artifact { classifier = "dev" }; isTransitive = false }
compileOnly(libs.lombok) { isTransitive = false }
annotationProcessor(libs.lombok)
compileOnly(libs.annotations)
compileOnly(libs.log4j.api)
// 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")}")
compileOnly(libs.guava)
testRuntimeOnly(libs.guava)
testRuntimeOnly(libs.fastutil)
testRuntimeOnly(libs.joml) { isTransitive = false }
testFixturesCompileOnly(sourceSets["stubs"].output)
testFixturesApi(platform(libs.junit.bom))
testFixturesApi(libs.junit.jupiter.api)
testFixturesApi(libs.lwjgl2)
// Test
testImplementation(sourceSets["stubs"].output)
testImplementation(platform("org.junit:junit-bom:${property("junitBomVersion")}"))
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.lwjgl.lwjgl:lwjgl:${property("lwjglVersion")}")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
testRuntimeOnly("org.apache.logging.log4j:log4j-core:${property("log4jVersion")}")
testRuntimeOnly("org.embeddedt.celeritas:celeritas-lwjgl2-service:${property("celeritasVersion")}") { isTransitive = false }
testRuntimeOnly("xyz.wagyourtail.jvmdowngrader:jvmdowngrader-java-api:${property("jvmDowngraderVersion")}:downgraded-8")
testRuntimeOnly("org.apache.commons:commons-lang3:${property("commonsLang3Version")}")
testImplementation(platform(libs.junit.bom))
testImplementation(libs.junit.jupiter)
testImplementation(libs.asm.tree.test)
testImplementation(libs.lwjgl2)
testRuntimeOnly(libs.junit.platform.launcher)
testRuntimeOnly(libs.log4j.core)
testImplementation(libs.celeritas.common) { isTransitive = false }
testRuntimeOnly(libs.celeritas.lwjgl2.service) { isTransitive = false }
testRuntimeOnly(libs.jvmdowngrader.java.api) { artifact { classifier = "downgraded-8" } }
testRuntimeOnly(libs.commons.lang3)
}
val depsToDowngrade by configurations.creating {
@@ -114,14 +151,15 @@ val depsToDowngrade by configurations.creating {
}
dependencies {
depsToDowngrade("net.minecraftforge:eventbus:${property("eventbusVersion")}")
depsToDowngrade("org.embeddedt.celeritas:celeritas-common:${property("celeritasVersion")}") { isTransitive = false }
depsToDowngrade(libs.eventbus)
depsToDowngrade(libs.celeritas.common) { isTransitive = false }
}
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) {
@@ -131,23 +169,41 @@ val downgradeDepsForTest by tasks.registering(DowngradeFiles::class) {
val downgradeMainClasses by tasks.registering(DowngradeFiles::class) {
inputCollection = sourceSets["main"].output.classesDirs.plus(sourceSets["stubs"].output.classesDirs)
classpath = sourceSets["main"].compileClasspath
(sourceSets["main"].output.classesDirs.files + sourceSets["stubs"].output.classesDirs.files)
.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
dependsOn(tasks.named("classes"), tasks.named("stubsClasses"))
}
val downgradeTestFixturesClasses by tasks.registering(DowngradeFiles::class) {
inputCollection = sourceSets["testFixtures"].output.classesDirs
classpath = sourceSets["testFixtures"].compileClasspath
sourceSets["testFixtures"].output.classesDirs.files.forEach { outputs.dir(temporaryDir.resolve(it.name)) }
dependsOn(tasks.named("testFixturesClasses"))
}
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"))
}
tasks.test {
useJUnitPlatform()
val GL_TASK_TAGS = mapOf(
"glCompatTest" to "gl-compat",
"glCoreTest" to "gl-core",
"glSharedTest" to "gl-shared",
)
fun Test.configureGlsmJava8() {
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)
dependsOn(downgradeMainClasses, downgradeTestClasses, downgradeTestFixturesClasses, downgradeDepsForTest)
val mainClassesDirs = sourceSets["main"].output.classesDirs
.plus(sourceSets["stubs"].output.classesDirs)
@@ -161,13 +217,96 @@ tasks.test {
classpath = downgradedTest
.plus(downgradedMain)
.plus(downgradedDeps)
.plus(classpath.minus(mainClassesDirs).minus(testClassesDirs).minus(depsToDowngrade))
.plus(files(downgradeTestFixturesClasses.map { it.outputCollection }))
.plus(sourceSets["test"].runtimeClasspath.minus(mainClassesDirs).minus(testClassesDirs)
.minus(sourceSets["testFixtures"].output.classesDirs).minus(depsToDowngrade))
val extractNatives = rootProject.tasks.named("extractNatives2")
dependsOn(extractNatives)
jvmArgs("-Djava.library.path=${extractNatives.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}")
}
tasks.test {
useJUnitPlatform {
excludeTags = GL_TASK_TAGS.values.toSet() + "lwjgl3"
}
filter {
excludeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator*Test")
}
configureGlsmJava8()
}
val glTestTasks = GL_TASK_TAGS.map { (taskName, tag) ->
tasks.register<Test>(taskName) {
description = "Runs the $tag GL tests in their own JVM, so they get their own Display and GL profile."
group = "verification"
useJUnitPlatform {
includeTags = setOf(tag)
}
configureGlsmJava8()
}
}
(listOf(tasks.test) + glTestTasks).zipWithNext { earlier, later ->
later.configure { mustRunAfter(earlier) }
}
tasks.test { finalizedBy(glTestTasks) }
val verifyTestsRan by tasks.registering {
dependsOn(tasks.test, glTestTasks)
}
(listOf(tasks.test) + glTestTasks).forEach { testTask ->
val name = testTask.name
val verify = tasks.register("verify${name.replaceFirstChar { it.uppercase() }}Ran") {
val resultsDir = layout.buildDirectory.dir("test-results/$name")
dependsOn(testTask)
doLast {
val dir = resultsDir.get().asFile
val xmls = dir.listFiles { f -> f.name.startsWith("TEST-") && f.name.endsWith(".xml") } ?: emptyArray()
check(xmls.isNotEmpty()) {
":glsm:$name produced no TEST-*.xml in $dir - test task likely went NO-SOURCE"
}
}
}
verifyTestsRan.configure { dependsOn(verify) }
}
tasks.check { dependsOn(verifyTestsRan) }
val spirvTest by tasks.registering(Test::class) {
description = "Runs SpirvShaderTranslator* tests against native shaderc + spvc on Java 21."
group = "verification"
useJUnitPlatform()
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(21)
}
testClassesDirs = sourceSets["test"].output.classesDirs
classpath = sourceSets["test"].runtimeClasspath.filter { file ->
// Strip the LWJGL2 jars - they collide with LWJGL3's sealed org.lwjgl package.
!file.name.startsWith("lwjgl-2.") && !file.name.startsWith("lwjgl_util-") && !file.name.startsWith("lwjgl-platform-")
}
filter {
includeTestsMatching("com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator*Test")
includeTestsMatching("com.gtnewhorizons.angelica.glsm.ffp.FFPUniformBlockSpirvLayoutTest")
}
// libshaderc.so / libspirv-cross.so are extracted by extractNatives3.
val extractNatives3 = rootProject.tasks.named("extractNatives3")
dependsOn(extractNatives3)
jvmArgs(
"-Djava.library.path=${extractNatives3.get().property("destinationFolder").let { (it as DirectoryProperty).asFile.get().path }}",
"--enable-native-access=ALL-UNNAMED"
)
testLogging {
showStandardStreams = true
}
}
tasks.named("check") { dependsOn(spirvTest) }
publishing {
publications {
create<MavenPublication>("mavenJava") {
-14
View File
@@ -1,14 +0,0 @@
lwjglVersion = 2.9.4-nightly-20150209
eventbusVersion = 7.0-beta.15
celeritasVersion = 2.5.3-GTNH
lombokVersion = 1.18.42
log4jVersion = 2.0-beta9
guavaVersion = 18.0
gtnhlibVersion = 0.9.36
glslTransformLibVersion = 0.2.0-26.g5ee7d96-GTNH
antlr4RuntimeVersion = 4.13.2
jcppVersion = 1.4.14
jetbrainsAnnotationsVersion = 26.0.2
junitBomVersion = 5.14.1
jvmDowngraderVersion = 1.3.5
commonsLang3Version = 3.17.0
@@ -0,0 +1,124 @@
package com.gtnewhorizons.angelica.config;
import net.minecraft.launchwrapper.Launch;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Locale;
public final class SystemProperties {
private static final Logger LOGGER = LogManager.getLogger("SystemProperties");
// Backend selection
public static final String KEY_USE_SDL_GPU = "angelica.sdlgpu.enable";
public static final boolean USE_SDL_GPU = Boolean.getBoolean(KEY_USE_SDL_GPU);
public static final String GL_PROFILE = System.getProperty("angelica.glProfile", "");
public static final boolean DISABLE_NVIDIA_WORKAROUND = Boolean.getBoolean("angelica.disableNvidiaWorkaround");
public static final boolean DISABLE_LTW_WORKAROUND = Boolean.getBoolean("angelica.disableLtwWorkaround");
// Redirector
public static final UnmappedGLMode UNMAPPED_GL = parseEnum("angelica.unmappedGL", UnmappedGLMode.WARN, UnmappedGLMode.WARN, UnmappedGLMode.class);
public static final boolean FFP_WARN_ON_UNSUPPORTED = Boolean.getBoolean("angelica.ffp.warnOnUnsupported");
// SDL-GPU
public static final String KEY_SDL_GPU_DRIVER = "angelica.sdlgpu.driver";
public static final String SDL_GPU_DRIVER = System.getProperty(KEY_SDL_GPU_DRIVER, "");
public static final boolean SDL_GPU_DEBUG = Boolean.getBoolean("angelica.sdlgpu.debug");
private static final SdlAssertionMode ENCODER_ASSERTIONS = parseEnum("angelica.sdlgpu.encoderAssertions", SdlAssertionMode.OFF, SdlAssertionMode.WARN, SdlAssertionMode.class);
public static final boolean SDL_ENCODER_ASSERTIONS = ENCODER_ASSERTIONS != SdlAssertionMode.OFF;
public static final boolean SDL_ENCODER_ASSERTIONS_FATAL = ENCODER_ASSERTIONS == SdlAssertionMode.FATAL;
public static final boolean SDL_VERIFY_STATE_SYNC = Boolean.getBoolean("angelica.sdlgpu.verifyStateSync");
public static final boolean SDL_VERIFY_PER_FRAME_UNIFORM_BLOCK = Boolean.getBoolean("angelica.sdlgpu.verifyPerFrameUniformBlock");
// Tracy
public static final boolean TRACY = Boolean.getBoolean("angelica.tracy");
public static final boolean TRACY_FINE_ZONES = Boolean.getBoolean("angelica.tracy.fineZones");
public static final String TRACY_DIR = System.getProperty("angelica.tracy.dir", "angelica" + File.separator + "natives" + File.separator + "tracy");
public static final int TRACY_MAX_SRC_LOCS = Math.max(16, Integer.getInteger("angelica.tracy.maxSrcLocs", 4096));
// Flyby
public static final String FLYBY_ROUTE = System.getProperty("angelica.flyby.route", "");
public static final boolean FLYBY_WAIT_FOR_TRACY = Boolean.getBoolean("angelica.flyby.waitForTracy");
public static final int FLYBY_WARMUP_TICKS = Integer.getInteger("angelica.flyby.warmupTicks", 400);
public static final int FLYBY_LENGTH = Integer.getInteger("angelica.flyby.length", 0); // Blocks for moving routes, ticks for stationary
public static final double FLYBY_SPEED = parseDouble("angelica.flyby.speed"); // Travel speed in blocks per tick
public static final boolean FLYBY_EXIT_WHEN_DONE = Boolean.getBoolean("angelica.flyby.exitWhenDone");
// Debug
public static final boolean LWJGL_DEBUG = Boolean.getBoolean("org.lwjgl.util.Debug");
public static final boolean DEBUG_MARKERS = Boolean.getBoolean("angelica.debug.markers");
public static final boolean DEBUG_F3_DETAIL = Boolean.getBoolean("angelica.debug.f3Detail");
public static final boolean ENABLE_TEST_BLOCKS = Boolean.getBoolean("angelica.debug.testBlocks");
public static final boolean DUMP_CLASS = Boolean.getBoolean("angelica.debug.dumpClass");
public static final boolean DUMP_SHADERS = Boolean.getBoolean("angelica.debug.dumpShaders") || isDeobf();
public static final boolean REDIRECTOR_LOGSPAM = Boolean.getBoolean("angelica.debug.redirectorLogspam");
public static final boolean DEBUG_DISPLAY_LISTS = Boolean.getBoolean("angelica.debug.displayLists");
public static final boolean LOG_DISPLAY_LIST_COMPILATION = Boolean.getBoolean("angelica.debug.displayLists.compilation");
public static final boolean FORCE_ORPHAN_STREAMING = Boolean.getBoolean("angelica.debug.forceOrphanStreaming");
public static final String SHADER_DUMP_ROOT = "angelica_dumps";
// Set by us, read by celeritas
public static final String KEY_CELERITAS_ENABLE_GL_DEBUG = "celeritas.enableGLDebug";
public static Path shaderDumpDir(String phase) {
return DUMP_SHADERS ? Paths.get(SHADER_DUMP_ROOT, phase) : null;
}
public enum UnmappedGLMode {
OFF,
WARN,
FAIL,
STRICT;
public boolean detects() {
return this != OFF;
}
public boolean failsUnmapped() {
return this == FAIL || this == STRICT;
}
public boolean failsAwareUnroutable() {
return this == STRICT;
}
}
private enum SdlAssertionMode {
OFF,
WARN,
FATAL
}
private static <E extends Enum<E>> E parseEnum(String key, E whenAbsent, E whenInvalid, Class<E> type) {
final String raw = System.getProperty(key);
if (raw == null || raw.isEmpty()) return whenAbsent;
try {
return Enum.valueOf(type, raw.trim().toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
LOGGER.warn("Unrecognized value '{}' for -D{}; using {}. Valid values: {}", raw, key, whenInvalid, Arrays.toString(type.getEnumConstants()));
return whenInvalid;
}
}
private static double parseDouble(String key) {
final String raw = System.getProperty(key);
if (raw == null || raw.isEmpty()) return 0.0D;
try {
return Double.parseDouble(raw);
} catch (NumberFormatException e) {
return 0.0D;
}
}
private static boolean isDeobf() {
return Launch.blackboard != null && Boolean.TRUE.equals(Launch.blackboard.get("fml.deobfuscatedEnvironment"));
}
private SystemProperties() {}
}
@@ -0,0 +1,76 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Locale;
import java.util.function.UnaryOperator;
/** Decides whether instrumentation is worth emitting this frame. */
public final class CaptureGate {
private static final Logger LOGGER = LogManager.getLogger("Angelica");
private static final boolean IS_LINUX = System.getProperty("os.name", "").toLowerCase(Locale.ROOT).contains("linux");
private static final Path PROC_SELF_MAPS = Paths.get("/proc/self/maps");
private static final String[] CAPTURE_ENV = { "RENDERDOC_CAPTURE_KEYPRESS", "ENABLE_VULKAN_RENDERDOC_CAPTURE", "RENDERDOC_HOOK_EGL" };
private static final String[] CAPTURE_LIBS = { "renderdoc", "glxtrace", "egltrace", "apitrace" };
public static final boolean FORCED = SystemProperties.DEBUG_MARKERS || SystemProperties.LWJGL_DEBUG;
public static final boolean TOOL_ATTACHED = detectCaptureTool();
public static boolean markersThisFrame = true;
private CaptureGate() {}
public static void refresh() {
markersThisFrame = gateValue(FORCED, TOOL_ATTACHED, Tracy.isConnected());
}
static boolean gateValue(boolean forced, boolean toolAttached, boolean tracyConnected) {
return forced || toolAttached || tracyConnected;
}
public static boolean enabledAtStartup() {
return FORCED || TOOL_ATTACHED;
}
private static boolean detectCaptureTool() {
if (detectFromEnv(System::getenv)) {
LOGGER.info("Capture tool detected via environment; instrumentation enabled");
return true;
}
if (!IS_LINUX) return false;
try {
if (detectFromMaps(Files.readAllLines(PROC_SELF_MAPS, StandardCharsets.ISO_8859_1))) {
LOGGER.info("Capture tool library detected in process map; instrumentation enabled");
return true;
}
} catch (IOException e) {
LOGGER.warn("Could not read {} for capture tool detection: {}", PROC_SELF_MAPS, e.toString());
}
return false;
}
static boolean detectFromEnv(UnaryOperator<String> env) {
for (String key : CAPTURE_ENV) {
if (env.apply(key) != null) return true;
}
return false;
}
static boolean detectFromMaps(List<String> mapLines) {
for (int i = 0; i < mapLines.size(); i++) {
final String line = mapLines.get(i).toLowerCase(Locale.ROOT);
for (String lib : CAPTURE_LIBS) {
if (line.contains(lib)) return true;
}
}
return false;
}
}
@@ -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,14 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.vertex.VertexFormatElement.Usage;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import com.gtnewhorizons.angelica.glsm.shader.SpirvShaderTranslator;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.lwjgl.opengl.GL20;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.ShaderParser;
import org.taumc.glsl.Transformer;
@@ -9,9 +17,12 @@ 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.HashMap;
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;
@@ -27,12 +38,12 @@ import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACK
* <ul>
* <li>Matrix builtin replacement (gl_ModelViewMatrix, gl_ProjectionMatrix, etc.)</li>
* <li>Vertex attribute replacement (gl_Vertex, gl_Color, gl_MultiTexCoord0/1, gl_Normal)</li>
* <li>gl_TexCoord[N] varying array → per-index in/out declarations</li>
* <li>Texture function renames (texture2D → texture, etc.)</li>
* <li>Fragment output handling (gl_FragColor → layout-qualified out declarations)</li>
* <li>gl_TexCoord[N] varying array -> per-index in/out declarations</li>
* <li>Texture function renames (texture2D -> texture, etc.)</li>
* <li>Fragment output handling (gl_FragColor -> layout-qualified out declarations)</li>
* <li>Fog builtins (gl_Fog, gl_FogFragCoord)</li>
* <li>gl_FrontColor → local variable in vertex shaders</li>
* <li>shadow2D/shadow2DLod → texture/textureLod with vec4 wrapping</li>
* <li>gl_FrontColor -> local variable in vertex shaders</li>
* <li>shadow2D/shadow2DLod -> texture/textureLod with vec4 wrapping</li>
* <li>Version upgrade to 330 core minimum</li>
* <li>Reserved word pre-parse renaming (texture-as-variable, sample, etc.)</li>
* </ul>
@@ -65,30 +76,59 @@ public class CompatShaderTransformer {
"gl_NormalMatrix", "angelica_NormalMatrix"
);
private static final Path DUMP_DIR;
private static final AtomicInteger dumpCounter = new AtomicInteger(0);
static {
DUMP_DIR = Boolean.parseBoolean(System.getProperty("angelica.dumpShaders", "false")) ? Paths.get("compat_shaders") : null;
}
private static final int CACHE_SIZE = 32;
private static final int CACHE_SIZE = 256;
private record CacheKey(String source, boolean isFragment) {}
private static final Map<CacheKey, String> cache = Collections.synchronizedMap(
new LinkedHashMap<>(32, 0.75f, true) {
new LinkedHashMap<>(CACHE_SIZE, 0.75f, true) {
@Override protected boolean removeEldestEntry(Map.Entry<CacheKey, String> eldest) {
return size() > CACHE_SIZE;
}
return size() > CACHE_SIZE;
}
});
private record EsCacheKey(String source, int glShaderType, boolean isFragment) {}
private static final Map<EsCacheKey, String> esCache = Collections.synchronizedMap(
new LinkedHashMap<>(CACHE_SIZE, 0.75f, true) {
@Override protected boolean removeEldestEntry(Map.Entry<EsCacheKey, String> eldest) {
return size() > CACHE_SIZE;
}
});
public static void clearCache() {
cache.clear();
esCache.clear();
}
/**
* Transform a mod shader source for core profile compatibility.
*/
public static void prewarm(String source, int glShaderType, boolean isFragment) {
if (!RenderSystem.isGLES()) return;
final EsCacheKey key = new EsCacheKey(source, glShaderType, isFragment);
if (esCache.containsKey(key)) return;
transform(source, glShaderType, isFragment);
}
/** Transform a mod shader for core profile; under GLES, also rewrites to ES 320. */
public static String transform(String source, boolean isFragment) {
return transform(source, isFragment ? GL20.GL_FRAGMENT_SHADER : GL20.GL_VERTEX_SHADER, isFragment);
}
/** Variant carrying the exact GL shader type so the GLES re-emit dispatches to the right shaderc kind (geometry/tess/compute). */
public static String transform(String source, int glShaderType, boolean isFragment) {
if (!source.isEmpty() && source.charAt(source.length() - 1) == '\0') {
source = source.substring(0, source.length() - 1);
}
final boolean gles = RenderSystem.isGLES();
if (gles) {
final EsCacheKey esKey = new EsCacheKey(source, glShaderType, isFragment);
final String esCached = esCache.get(esKey);
if (esCached != null) {
dumpShader(source, esCached, isFragment, needsTransformation(source));
return esCached;
}
}
final boolean needsTransform = needsTransformation(source);
String result;
if (!needsTransform) {
@@ -97,30 +137,44 @@ public class CompatShaderTransformer {
final CacheKey key = new CacheKey(source, isFragment);
final String cached = cache.get(key);
if (cached != null) {
dumpShader(source, cached, isFragment, needsTransform);
return cached;
result = cached;
} else {
try {
result = transformInternal(source, isFragment);
cache.put(key, result);
} catch (Exception e) {
GLStateManager.LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
result = fixupVersion(source);
}
}
}
if (gles) {
final String preGles = result;
try {
result = transformInternal(source, isFragment);
cache.put(key, result);
} catch (Exception e) {
GLStateManager.LOGGER.warn("CompatShaderTransformer: AST transformation failed, falling back to version fixup only", e);
result = fixupVersion(source);
result = toGLES(result, glShaderType, isFragment);
} catch (RuntimeException e) {
dumpShader(source, preGles, isFragment, needsTransform);
throw e;
}
esCache.put(new EsCacheKey(source, glShaderType, isFragment), result);
}
dumpShader(source, result, isFragment, needsTransform);
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();
}
@@ -134,24 +188,24 @@ public class CompatShaderTransformer {
final int targetVersion = Math.max(declaredVersion, RENDER_BACKEND != null ? RENDER_BACKEND.getMinGLSLVersion() : 330);
// Pre-parse reserved word renaming — prevents ANTLR parse failures
// Pre-parse reserved word renaming - prevents ANTLR parse failures
source = GlslTransformUtils.replaceTexture(source);
source = GlslTransformUtils.renameReservedWords(source, targetVersion);
final ShaderParser.ParsedShader parsedShader = ShaderParser.parseShader(source);
final Transformer transformer = new Transformer(parsedShader.full());
final GlslTransformUtils.QuietParse parsed = GlslTransformUtils.parseBothQuiet(source);
final Transformer transformer = new Transformer(parsed.full());
injectMatrixUniforms(transformer);
transformFog(transformer, isFragment, source);
// gl_FrontLightModelProduct.sceneColor → angelica_SceneColor uniform
// gl_FrontLightModelProduct.sceneColor -> angelica_SceneColor uniform
if (source.contains("gl_FrontLightModelProduct")) {
transformer.injectVariable("uniform vec4 angelica_SceneColor;");
transformer.replaceExpression("gl_FrontLightModelProduct.sceneColor", "angelica_SceneColor");
}
// gl_LightSource[i] → struct + uniform array (both via injectFunction to keep struct before uniform)
// gl_LightSource[i] -> struct + uniform array (both via injectFunction to keep struct before uniform)
if (source.contains("gl_LightSource")) {
transformer.injectFunction("struct angelica_LightSourceParameters {"
+ "vec4 ambient;vec4 diffuse;vec4 specular;vec4 position;vec4 halfVector;"
@@ -162,7 +216,7 @@ public class CompatShaderTransformer {
transformer.rename("gl_LightSource", "angelica_LightSource");
}
// gl_FrontMaterial → struct + uniform (both via injectFunction to keep struct before uniform)
// gl_FrontMaterial -> struct + uniform (both via injectFunction to keep struct before uniform)
if (source.contains("gl_FrontMaterial")) {
transformer.injectFunction("struct angelica_MaterialParameters {"
+ "vec4 emission;vec4 ambient;vec4 diffuse;vec4 specular;float shininess;"
@@ -171,7 +225,7 @@ public class CompatShaderTransformer {
transformer.rename("gl_FrontMaterial", "angelica_FrontMaterial");
}
// gl_FrontColor (vertex) → gl_Color (fragment) varying chain
// gl_FrontColor (vertex) -> gl_Color (fragment) varying chain
// Vertex side is unconditional: fragment may read gl_Color without vertex writing gl_FrontColor.
// Uses angelica_ prefix to avoid colliding with user-declared varyings (e.g. "v_Color").
if (!isFragment) {
@@ -179,8 +233,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;");
@@ -188,7 +243,7 @@ public class CompatShaderTransformer {
}
}
// gl_TexCoord[N] varying array → per-index in/out declarations
// gl_TexCoord[N] varying array -> per-index in/out declarations
final Set<Integer> texCoordIndices = new HashSet<>();
transformer.renameArray("gl_TexCoord", "angelica_TexCoord", texCoordIndices);
for (Integer i : texCoordIndices) {
@@ -215,7 +270,7 @@ public class CompatShaderTransformer {
transformer.renameAndWrapShadow("shadow2DLod", "textureLod");
final String versionDirective = "#version " + targetVersion + " core\n";
final String extensions = VERSION_PATTERN.matcher(GlslTransformUtils.getFormattedShader(parsedShader.pre(), "")).replaceFirst("").trim();
final String extensions = VERSION_PATTERN.matcher(GlslTransformUtils.getFormattedShader(parsed.pre(), "")).replaceFirst("").trim();
// Preserve #define directives
final StringBuilder defines = new StringBuilder();
@@ -231,7 +286,7 @@ public class CompatShaderTransformer {
// Restore pre-parse renames
String output = GlslTransformUtils.restoreReservedWords(result.toString());
// Core profile: attribute → in, varying → out (vertex) / in (fragment)
// Core profile: attribute -> in, varying -> out (vertex) / in (fragment)
output = fixupQualifiers(output, isFragment);
return output;
@@ -299,7 +354,8 @@ public class CompatShaderTransformer {
transformer.injectVariable("uniform float angelica_FogEnd;");
transformer.injectVariable("uniform vec4 angelica_FogColor;");
transformer.injectFunction("struct angelica_FogParameters {vec4 color;float density;float start;float end;float scale;};");
transformer.injectFunction("angelica_FogParameters angelica_Fog = angelica_FogParameters("
transformer.injectFunction("angelica_FogParameters angelica_Fog;");
transformer.prependMain("angelica_Fog = angelica_FogParameters("
+ "angelica_FogColor, angelica_FogDensity, angelica_FogStart, angelica_FogEnd, "
+ "1.0 / (angelica_FogEnd - angelica_FogStart));");
}
@@ -307,25 +363,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,19 +390,159 @@ 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 Path dir = SystemProperties.shaderDumpDir("compat");
if (dir == null) return;
final int id = dumpCounter.getAndIncrement();
final String suffix = isFragment ? ".frag.glsl" : ".vert.glsl";
try {
Files.createDirectories(DUMP_DIR);
Files.createDirectories(dir);
// Capture caller info for identification
final String caller = identifyCaller();
final String header = "// Compat shader dump #" + id + " (" + (isFragment ? "fragment" : "vertex") + ")"
+ "\n// Transformed: " + wasTransformed
+ "\n// Caller: " + caller + "\n\n";
Files.writeString(DUMP_DIR.resolve(id + "_original" + suffix), header + original, StandardCharsets.UTF_8);
Files.writeString(DUMP_DIR.resolve(id + "_transformed" + suffix), header + transformed, StandardCharsets.UTF_8);
Files.writeString(dir.resolve(id + "_original" + suffix), header + original, StandardCharsets.UTF_8);
Files.writeString(dir.resolve(id + "_transformed" + suffix), header + transformed, StandardCharsets.UTF_8);
} catch (IOException e) {
GLStateManager.LOGGER.warn("Failed to dump compat shader: {}", e.getMessage());
}
@@ -374,10 +570,10 @@ public class CompatShaderTransformer {
return source;
}
// Patterns for parsing fragment shader in declarations to generate passthrough vertex shaders
private static final Pattern FRAG_IN_COLOR = Pattern.compile("\\bin\\s+vec4\\s+angelica_FrontColor\\b");
private static final Pattern FRAG_IN_TEXCOORD = Pattern.compile("\\bin\\s+vec4\\s+angelica_TexCoord(\\d+)\\b");
private static final Pattern FRAG_IN_FOGCOORD = Pattern.compile("\\bin\\s+float\\s+angelica_FogFragCoord\\b");
private static final String PRECISION_QUALIFIER = "(?:highp\\s+|mediump\\s+|lowp\\s+)?";
private static final Pattern FRAG_IN_COLOR = Pattern.compile("\\bin\\s+" + PRECISION_QUALIFIER + "(vec[234])\\s+angelica_FrontColor\\b");
private static final Pattern FRAG_IN_TEXCOORD = Pattern.compile("\\bin\\s+" + PRECISION_QUALIFIER + "(vec[234])\\s+angelica_TexCoord(\\d+)\\b");
private static final Pattern FRAG_IN_FOGCOORD = Pattern.compile("\\bin\\s+" + PRECISION_QUALIFIER + "float\\s+angelica_FogFragCoord\\b");
/**
* Generate a passthrough vertex shader for a fragment-only program.
@@ -398,22 +594,26 @@ public class CompatShaderTransformer {
final StringBuilder varyings = new StringBuilder();
final StringBuilder assignments = new StringBuilder();
if (FRAG_IN_COLOR.matcher(fragmentSource).find()) {
sb.append("layout(location = 1) in vec4 a_Color;\n");
varyings.append("out vec4 angelica_FrontColor;\n");
final Matcher colorMatcher = FRAG_IN_COLOR.matcher(fragmentSource);
if (colorMatcher.find()) {
final String colorType = colorMatcher.group(1);
sb.append("layout(location = 1) in ").append(colorType).append(" a_Color;\n");
varyings.append("out ").append(colorType).append(" angelica_FrontColor;\n");
assignments.append(" angelica_FrontColor = a_Color;\n");
}
final Matcher texCoordMatcher = FRAG_IN_TEXCOORD.matcher(fragmentSource);
final Set<Integer> texCoordIndices = new HashSet<>();
final Map<Integer, String> texCoordTypes = new HashMap<>();
while (texCoordMatcher.find()) {
texCoordIndices.add(Integer.parseInt(texCoordMatcher.group(1)));
texCoordTypes.putIfAbsent(Integer.parseInt(texCoordMatcher.group(2)), texCoordMatcher.group(1));
}
for (int i : texCoordIndices) {
for (Map.Entry<Integer, String> e : texCoordTypes.entrySet()) {
final int i = e.getKey();
final String type = e.getValue();
// PRIMARY_UV=2, SECONDARY_UV=3. Only indices 0 and 1 are supported; higher indices collide at location 3.
final int location = i == 0 ? 2 : 3;
sb.append("layout(location = ").append(location).append(") in vec4 a_TexCoord").append(i).append(";\n");
varyings.append("out vec4 angelica_TexCoord").append(i).append(";\n");
sb.append("layout(location = ").append(location).append(") in ").append(type).append(" a_TexCoord").append(i).append(";\n");
varyings.append("out ").append(type).append(" angelica_TexCoord").append(i).append(";\n");
assignments.append(" angelica_TexCoord").append(i).append(" = a_TexCoord").append(i).append(";\n");
}
@@ -431,6 +631,23 @@ public class CompatShaderTransformer {
return sb.toString();
}
/** 330-core -> GLSL ES 320 via shaderc + SPIRV-Cross. Throws on shaderc failure. */
public static String toGLES(String source, int glShaderType, boolean isFragment) {
final Matcher m = VERSION_PATTERN.matcher(source);
if (m.find() && "es".equalsIgnoreCase(m.group(2))) return source;
if (glShaderType == 0) throw new IllegalArgumentException("toGLES requires a valid GL shader type");
final String translated = SpirvShaderTranslator.glslToGlslEs(source, glShaderType, "compat");
if (translated == null) {
throw new IllegalStateException("SpirvShaderTranslator failed for shader kind 0x"
+ Integer.toHexString(glShaderType) + "; see preceding shaderc error + run/client/angelica_dumps/compat/");
}
return translated;
}
public static String toGLES(String source, boolean isFragment) {
return toGLES(source, isFragment ? GL20.GL_FRAGMENT_SHADER : GL20.GL_VERTEX_SHADER, isFragment);
}
/** Ensure #version is at least 330 core, strip 'compatibility' profile. */
private static String fixupVersion(String source) {
final Matcher m = VERSION_PATTERN.matcher(source);
@@ -13,12 +13,14 @@ 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 +63,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 +96,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 +112,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 +129,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 +139,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 +161,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
final int mvGen = GLStateManager.mvGeneration;
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;
// Matrix uniforms — skip if this program's storage already holds the current generation
final int mvGen = GLStateManager.getMvGeneration();
final int mvLinearGen = GLStateManager.getMvLinearGeneration();
final int projGen = GLStateManager.getProjGeneration();
final int texMatGen = GLStateManager.getTexMatrixGeneration();
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;
final int fragGen = GLStateManager.getFragmentGeneration();
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;
final int litGen = GLStateManager.getLightingGeneration();
if (litGen != pu.lastLightingGen) {
pu.lastLightingGen = litGen;
if (locs[LOC_SCENE_COLOR] != -1) uploadSceneColor(locs);
uploadLightSources(locs);
uploadMaterial(locs);
@@ -191,15 +211,17 @@ 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;
final int cpGen = GLStateManager.getClipPlaneGeneration();
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 +238,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 +280,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 +438,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
@@ -76,29 +76,29 @@ public class Feature {
*/
private static void addTextureGenStates(Set<IStateStack<?>> collection) {
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
collection.add(GLStateManager.textures.getTexGenSStates(i));
collection.add(GLStateManager.textures.getTexGenTStates(i));
collection.add(GLStateManager.textures.getTexGenRStates(i));
collection.add(GLStateManager.textures.getTexGenQStates(i));
collection.add(GLStateManager.getTextures().getTexGenSStates(i));
collection.add(GLStateManager.getTextures().getTexGenTStates(i));
collection.add(GLStateManager.getTextures().getTexGenRStates(i));
collection.add(GLStateManager.getTextures().getTexGenQStates(i));
}
}
static {
attribToFeatures.put(GL11.GL_COLOR_BUFFER_BIT, ImmutableSet.of(
GLStateManager.alphaTest // GL_ALPHA_TEST enable bit
, GLStateManager.alphaState // Alpha test function and reference value
, GLStateManager.blendMode // GL_BLEND enable bit
, GLStateManager.blendState // Blending source/destination functions, equation, blend color
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP enable bit
, GLStateManager.ditherState // GL_DITHER enable bit
, GLStateManager.drawBuffer // GL_DRAW_BUFFER setting
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP enable bit
, GLStateManager.logicOpMode // Logic op function
, GLStateManager.colorMask // Color-mode and index-mode writemasks
, GLStateManager.clearColor // Color-mode and index-mode clear values
GLStateManager.getAlphaTest()
, GLStateManager.getAlphaState()
, GLStateManager.getBlendMode()
, GLStateManager.getBlendState()
, GLStateManager.getColorLogicOpState()
, GLStateManager.getDitherState()
, GLStateManager.getDrawBuffer()
, GLStateManager.getIndexLogicOpState()
, GLStateManager.getLogicOpMode()
, GLStateManager.getColorMask()
, GLStateManager.getClearColor()
));
attribToFeatures.put(GL11.GL_CURRENT_BIT, ImmutableSet.of(
GLStateManager.color // Current RGBA color
GLStateManager.getColor()
, ShaderManager.getNormalStack() // Current normal vector
, ShaderManager.getTexCoordStack() // Current texture coordinates
// Current color index
@@ -110,78 +110,78 @@ public class Feature {
// GL_EDGE_FLAG flag
));
attribToFeatures.put(GL11.GL_DEPTH_BUFFER_BIT, ImmutableSet.of(
GLStateManager.depthTest // GL_DEPTH_TEST enable bit
, GLStateManager.depthState // Depth buffer test function
GLStateManager.getDepthTest()
, GLStateManager.getDepthState()
// Depth buffer clear value
// GL_DEPTH_WRITEMASK enable bit
));
final HashSet<IStateStack<?>> enableBits = new HashSet<>(ImmutableSet.of(
GLStateManager.alphaTest // GL_ALPHA_TEST flag
, GLStateManager.autoNormalState // GL_AUTO_NORMAL flag
, GLStateManager.blendMode // GL_BLEND flag
, GLStateManager.colorLogicOpState // GL_COLOR_LOGIC_OP flag
, GLStateManager.colorMaterial // GL_COLOR_MATERIAL
, GLStateManager.cullState // GL_CULL_FACE flag
, GLStateManager.depthTest // GL_DEPTH_TEST flag
, GLStateManager.ditherState // GL_DITHER flag
, GLStateManager.fogMode // GL_FOG flag
, GLStateManager.indexLogicOpState // GL_INDEX_LOGIC_OP flag
, GLStateManager.lightStates[0] // GL_LIGHT0
, GLStateManager.lightStates[1] // GL_LIGHT1
, GLStateManager.lightStates[2] // GL_LIGHT2
, GLStateManager.lightStates[3] // GL_LIGHT3
, GLStateManager.lightStates[4] // GL_LIGHT4
, GLStateManager.lightStates[5] // GL_LIGHT5
, GLStateManager.lightStates[6] // GL_LIGHT6
, GLStateManager.lightStates[7] // GL_LIGHT7
, GLStateManager.lightingState // GL_LIGHTING flag
, GLStateManager.lineSmoothState // GL_LINE_SMOOTH flag
, GLStateManager.lineStippleState // GL_LINE_STIPPLE flag
, GLStateManager.map1Color4State // GL_MAP1_COLOR_4
, GLStateManager.map1IndexState // GL_MAP1_INDEX
, GLStateManager.map1NormalState // GL_MAP1_NORMAL
, GLStateManager.map1TextureCoord1State // GL_MAP1_TEXTURE_COORD_1
, GLStateManager.map1TextureCoord2State // GL_MAP1_TEXTURE_COORD_2
, GLStateManager.map1TextureCoord3State // GL_MAP1_TEXTURE_COORD_3
, GLStateManager.map1TextureCoord4State // GL_MAP1_TEXTURE_COORD_4
, GLStateManager.map1Vertex3State // GL_MAP1_VERTEX_3
, GLStateManager.map1Vertex4State // GL_MAP1_VERTEX_4
, GLStateManager.map2Color4State // GL_MAP2_COLOR_4
, GLStateManager.map2IndexState // GL_MAP2_INDEX
, GLStateManager.map2NormalState // GL_MAP2_NORMAL
, GLStateManager.map2TextureCoord1State // GL_MAP2_TEXTURE_COORD_1
, GLStateManager.map2TextureCoord2State // GL_MAP2_TEXTURE_COORD_2
, GLStateManager.map2TextureCoord3State // GL_MAP2_TEXTURE_COORD_3
, GLStateManager.map2TextureCoord4State // GL_MAP2_TEXTURE_COORD_4
, GLStateManager.map2Vertex3State // GL_MAP2_VERTEX_3
, GLStateManager.map2Vertex4State // GL_MAP2_VERTEX_4
, GLStateManager.multisampleState // GL_MULTISAMPLE flag
, GLStateManager.normalizeState // GL_NORMALIZE flag
, GLStateManager.pointSmoothState // GL_POINT_SMOOTH flag
, GLStateManager.polygonOffsetPointState // GL_POLYGON_OFFSET_POINT flag
, GLStateManager.polygonOffsetLineState // GL_POLYGON_OFFSET_LINE flag
, GLStateManager.polygonOffsetFillState // GL_POLYGON_OFFSET_FILL flag
, GLStateManager.polygonSmoothState // GL_POLYGON_SMOOTH flag
, GLStateManager.polygonStippleState // GL_POLYGON_STIPPLE flag
, GLStateManager.rescaleNormalState // GL_RESCALE_NORMAL flag
, GLStateManager.sampleAlphaToCoverageState // GL_SAMPLE_ALPHA_TO_COVERAGE flag
, GLStateManager.sampleAlphaToOneState // GL_SAMPLE_ALPHA_TO_ONE flag
, GLStateManager.sampleCoverageState // GL_SAMPLE_COVERAGE flag
, GLStateManager.scissorTest // GL_SCISSOR_TEST flag
, GLStateManager.stencilTest // GL_STENCIL_TEST flag
GLStateManager.getAlphaTest()
, GLStateManager.getAutoNormalState()
, GLStateManager.getBlendMode()
, GLStateManager.getColorLogicOpState()
, GLStateManager.getColorMaterial()
, GLStateManager.getCullState()
, GLStateManager.getDepthTest()
, GLStateManager.getDitherState()
, GLStateManager.getFogMode()
, GLStateManager.getIndexLogicOpState()
, GLStateManager.getLightStates()[0] // GL_LIGHT0
, GLStateManager.getLightStates()[1] // GL_LIGHT1
, GLStateManager.getLightStates()[2] // GL_LIGHT2
, GLStateManager.getLightStates()[3] // GL_LIGHT3
, GLStateManager.getLightStates()[4] // GL_LIGHT4
, GLStateManager.getLightStates()[5] // GL_LIGHT5
, GLStateManager.getLightStates()[6] // GL_LIGHT6
, GLStateManager.getLightStates()[7] // GL_LIGHT7
, GLStateManager.getLightingState()
, GLStateManager.getLineSmoothState()
, GLStateManager.getLineStippleState()
, GLStateManager.getMap1Color4State()
, GLStateManager.getMap1IndexState()
, GLStateManager.getMap1NormalState()
, GLStateManager.getMap1TextureCoord1State()
, GLStateManager.getMap1TextureCoord2State()
, GLStateManager.getMap1TextureCoord3State()
, GLStateManager.getMap1TextureCoord4State()
, GLStateManager.getMap1Vertex3State()
, GLStateManager.getMap1Vertex4State()
, GLStateManager.getMap2Color4State()
, GLStateManager.getMap2IndexState()
, GLStateManager.getMap2NormalState()
, GLStateManager.getMap2TextureCoord1State()
, GLStateManager.getMap2TextureCoord2State()
, GLStateManager.getMap2TextureCoord3State()
, GLStateManager.getMap2TextureCoord4State()
, GLStateManager.getMap2Vertex3State()
, GLStateManager.getMap2Vertex4State()
, GLStateManager.getMultisampleState()
, GLStateManager.getNormalizeState()
, GLStateManager.getPointSmoothState()
, GLStateManager.getPolygonOffsetPointState()
, GLStateManager.getPolygonOffsetLineState()
, GLStateManager.getPolygonOffsetFillState()
, GLStateManager.getPolygonSmoothState()
, GLStateManager.getPolygonStippleState()
, GLStateManager.getRescaleNormalState()
, GLStateManager.getSampleAlphaToCoverageState()
, GLStateManager.getSampleAlphaToOneState()
, GLStateManager.getSampleCoverageState()
, GLStateManager.getScissorTest()
, GLStateManager.getStencilTest()
));
// Enable bits for the user-definable clipping planes
for(int i = 0; i < GLStateManager.clipPlaneStates.length; i++) {
enableBits.add(GLStateManager.clipPlaneStates[i]);
for(int i = 0; i < GLStateManager.getClipPlaneStates().length; i++) {
enableBits.add(GLStateManager.getClipPlaneStates()[i]);
}
// GL_TEXTURE_1D, GL_TEXTURE_2D, GL_TEXTURE_3D flags
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
enableBits.add(GLStateManager.textures.getTexture1DStates(i));
enableBits.add(GLStateManager.textures.getTextureUnitStates(i));
enableBits.add(GLStateManager.textures.getTexture3DStates(i));
enableBits.add(GLStateManager.getTextures().getTexture1DStates(i));
enableBits.add(GLStateManager.getTextures().getTextureUnitStates(i));
enableBits.add(GLStateManager.getTextures().getTexture3DStates(i));
}
// Flags GL_TEXTURE_GEN_x where x is S, T, R, or Q
@@ -196,8 +196,8 @@ public class Feature {
// GL_AUTO_NORMAL enable bit
));
attribToFeatures.put(GL11.GL_FOG_BIT, ImmutableSet.of(
GLStateManager.fogMode // GL_FOG enable bit
, GLStateManager.fogState // Fog color
GLStateManager.getFogMode()
, GLStateManager.getFogState()
// ^^ Fog density
// ^^ Linear fog start
// ^^ Linear fog end
@@ -214,42 +214,42 @@ public class Feature {
// GL_TEXTURE_COMPRESSION_HINT setting
));
attribToFeatures.put(GL11.GL_LIGHTING_BIT, ImmutableSet.of(
GLStateManager.colorMaterial // GL_COLOR_MATERIAL enable bit
, GLStateManager.colorMaterialFace // GL_COLOR_MATERIAL_FACE value
, GLStateManager.colorMaterialParameter // Color material parameters that are tracking the current color
, GLStateManager.lightModel // Ambient scene color, GL_LIGHT_MODEL_LOCAL_VIEWER, GL_LIGHT_MODEL_TWO_SIDE
, GLStateManager.lightingState // GL_LIGHTING enable bit
GLStateManager.getColorMaterial()
, GLStateManager.getColorMaterialFace()
, GLStateManager.getColorMaterialParameter()
, GLStateManager.getLightModel()
, GLStateManager.getLightingState()
// Enable bit for each light
, GLStateManager.lightStates[0] // GL_LIGHT0
, GLStateManager.lightStates[1] // GL_LIGHT1
, GLStateManager.lightStates[2] // GL_LIGHT2
, GLStateManager.lightStates[3] // GL_LIGHT3
, GLStateManager.lightStates[4] // GL_LIGHT4
, GLStateManager.lightStates[5] // GL_LIGHT5
, GLStateManager.lightStates[6] // GL_LIGHT6
, GLStateManager.lightStates[7] // GL_LIGHT7
, GLStateManager.getLightStates()[0] // GL_LIGHT0
, GLStateManager.getLightStates()[1] // GL_LIGHT1
, GLStateManager.getLightStates()[2] // GL_LIGHT2
, GLStateManager.getLightStates()[3] // GL_LIGHT3
, GLStateManager.getLightStates()[4] // GL_LIGHT4
, GLStateManager.getLightStates()[5] // GL_LIGHT5
, GLStateManager.getLightStates()[6] // GL_LIGHT6
, GLStateManager.getLightStates()[7] // GL_LIGHT7
// Ambient, diffuse, and specular intensity for each light
// Direction, position, exponent, and cutoff angle for each light
// Constant, linear, and quadratic attenuation factors for each light
, GLStateManager.lightDataStates[0]
, GLStateManager.lightDataStates[1]
, GLStateManager.lightDataStates[2]
, GLStateManager.lightDataStates[3]
, GLStateManager.lightDataStates[4]
, GLStateManager.lightDataStates[5]
, GLStateManager.lightDataStates[6]
, GLStateManager.lightDataStates[7]
, GLStateManager.getLightDataStates()[0]
, GLStateManager.getLightDataStates()[1]
, GLStateManager.getLightDataStates()[2]
, GLStateManager.getLightDataStates()[3]
, GLStateManager.getLightDataStates()[4]
, GLStateManager.getLightDataStates()[5]
, GLStateManager.getLightDataStates()[6]
, GLStateManager.getLightDataStates()[7]
// Ambient, diffuse, specular, and emissive color for each material
// Ambient, diffuse, and specular color indices for each material
// Specular exponent for each material
, GLStateManager.frontMaterial
, GLStateManager.backMaterial
, GLStateManager.shadeModelState // GL_SHADE_MODEL setting
, GLStateManager.getFrontMaterial()
, GLStateManager.getBackMaterial()
, GLStateManager.getShadeModelState()
));
attribToFeatures.put(GL11.GL_LINE_BIT, ImmutableSet.of(
GLStateManager.lineSmoothState // GL_LINE_SMOOTH flag
, GLStateManager.lineStippleState // GL_LINE_STIPPLE enable bit
, GLStateManager.lineState // Line stipple pattern, repeat counter, and width
GLStateManager.getLineSmoothState()
, GLStateManager.getLineStippleState()
, GLStateManager.getLineState()
));
attribToFeatures.put(GL11.GL_LIST_BIT, ImmutableSet.of(
// GL_LIST_BASE setting
@@ -274,31 +274,31 @@ public class Feature {
// GL_READ_BUFFER setting
));
attribToFeatures.put(GL11.GL_POINT_BIT, ImmutableSet.of(
GLStateManager.pointSmoothState // GL_POINT_SMOOTH flag
, GLStateManager.pointState // Point size
GLStateManager.getPointSmoothState()
, GLStateManager.getPointState()
));
attribToFeatures.put(GL11.GL_POLYGON_BIT, ImmutableSet.of(
GLStateManager.cullState // GL_CULL_FACE enable bit
, GLStateManager.polygonSmoothState // GL_POLYGON_SMOOTH flag
, GLStateManager.polygonStippleState // GL_POLYGON_STIPPLE enable bit
, GLStateManager.polygonOffsetFillState // GL_POLYGON_OFFSET_FILL flag
, GLStateManager.polygonOffsetLineState // GL_POLYGON_OFFSET_LINE flag
, GLStateManager.polygonOffsetPointState // GL_POLYGON_OFFSET_POINT flag
, GLStateManager.polygonState // GL_CULL_FACE_MODE, GL_FRONT_FACE, GL_POLYGON_MODE, GL_POLYGON_OFFSET_FACTOR/UNITS
GLStateManager.getCullState()
, GLStateManager.getPolygonSmoothState()
, GLStateManager.getPolygonStippleState()
, GLStateManager.getPolygonOffsetFillState()
, GLStateManager.getPolygonOffsetLineState()
, GLStateManager.getPolygonOffsetPointState()
, GLStateManager.getPolygonState()
));
attribToFeatures.put(GL11.GL_POLYGON_STIPPLE_BIT, ImmutableSet.of(
// Polygon stipple pattern
));
attribToFeatures.put(GL11.GL_SCISSOR_BIT, ImmutableSet.of(
GLStateManager.scissorTest // GL_SCISSOR_TEST enable bit
GLStateManager.getScissorTest()
// Scissor box
));
attribToFeatures.put(GL11.GL_STENCIL_BUFFER_BIT, ImmutableSet.of(
GLStateManager.stencilTest // GL_STENCIL_TEST enable bit
, GLStateManager.stencilState // Stencil function, ref, mask, ops, writemask, clear value
GLStateManager.getStencilTest()
, GLStateManager.getStencilState()
));
final Set<IStateStack<?>> textureAttribs = new HashSet<>(ImmutableSet.of(
GLStateManager.activeTextureUnit // Active texture unit
GLStateManager.getActiveTextureUnitStack()
// GL_TEXTURE_ENV_MODE + GL_TEXTURE_ENV_COLOR — now per-unit in TexEnvState (added below)
// Enable bits for the four texture coordinates
@@ -315,8 +315,8 @@ public class Feature {
// Current Texture Bindings - GL_TEXTURE_BINDING_2D + per-unit TexEnvState
for(int i = 0 ; i < GLStateManager.MAX_TEXTURE_UNITS; i++) {
textureAttribs.add(GLStateManager.textures.getTextureUnitBindings(i));
textureAttribs.add(GLStateManager.textures.getTexEnvState(i));
textureAttribs.add(GLStateManager.getTextures().getTextureUnitBindings(i));
textureAttribs.add(GLStateManager.getTextures().getTexEnvState(i));
}
// Enable bits GL_TEXTURE_GEN_x where x is S, T, R, or Q
@@ -326,14 +326,14 @@ public class Feature {
attribToFeatures.put(GL11.GL_TRANSFORM_BIT, ImmutableSet.of(
// Coefficients of the six clipping planes
// Enable bits for the user-definable clipping planes
GLStateManager.matrixMode
, GLStateManager.normalizeState // GL_NORMALIZE flag
, GLStateManager.rescaleNormalState // GL_RESCALE_NORMAL flag
GLStateManager.getMatrixMode()
, GLStateManager.getNormalizeState()
, GLStateManager.getRescaleNormalState()
));
attribToFeatures.put(GL11.GL_VIEWPORT_BIT, ImmutableSet.of(
// Depth range (near and far)
GLStateManager.viewportState
GLStateManager.getViewportState()
));
}
@@ -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 = GLTypes.sizeBytes(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) {
@@ -345,6 +361,27 @@ public class FeedbackManager {
}
break;
case GL11.GL_QUAD_STRIP:
for (int i = 0; i + 3 < count; i += 2) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(4);
emitVertex(reader.read(i), stride);
emitVertex(reader.read(i + 1), stride);
emitVertex(reader.read(i + 3), stride);
emitVertex(reader.read(i + 2), stride);
}
break;
case GL11.GL_POLYGON:
if (count >= 3) {
feedbackToken(GL11.GL_POLYGON_TOKEN);
feedbackToken(count);
for (int i = 0; i < count; i++) {
emitVertex(reader.read(i), stride);
}
}
break;
default:
throw new UnsupportedOperationException("Feedback mode: unsupported primitive mode 0x" + Integer.toHexString(mode));
}
@@ -0,0 +1,203 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizon.gtnhlib.client.renderer.stacks.IStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.AlphaStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BlendStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.BooleanStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.Color4Stack;
import com.gtnewhorizons.angelica.glsm.stacks.ColorMaskStack;
import com.gtnewhorizons.angelica.glsm.stacks.DepthStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.FogStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.IntegerStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.LightModelStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.LightStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.LineStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.MaterialStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.MatrixModeStack;
import com.gtnewhorizons.angelica.glsm.stacks.PointStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.PolygonStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.StencilStateStack;
import com.gtnewhorizons.angelica.glsm.stacks.ViewPortStateStack;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.Color4;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import com.gtnewhorizons.angelica.glsm.states.ImageUnitArray;
import com.gtnewhorizons.angelica.glsm.states.TextureUnitArray;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntStack;
import org.joml.Matrix4fStack;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.util.ArrayList;
import java.util.List;
class GLContextState {
final List<BooleanStateStack> allBooleanStates = new ArrayList<>();
private BooleanStateStack track(BooleanStateStack s) {
allBooleanStates.add(s);
return s;
}
public int programGeneration;
public int drawFramebufferGeneration;
public boolean poppingAttributes;
// modelview matrix changes (any: translation, rotation, scale)
public int mvGeneration;
public int mvLinearGeneration;
// projection matrix changes
public int projGeneration;
// texture matrix changes
public int texMatrixGeneration;
public int lightingGeneration;
// fog + alpha ref + overlay color
public int fragmentGeneration;
// current vertex color
public int colorGeneration;
// clip plane equation changes
public int clipPlaneGeneration;
public float overlayR = 0.0f;
public float overlayG = 0.0f;
public float overlayB = 0.0f;
public float overlayA = 0.0f;
public float shaderColorR = 1.0f;
public float shaderColorG = 1.0f;
public float shaderColorB = 1.0f;
public float shaderColorA = 1.0f;
// Deferred vertex attribute upload flags - set when state changes, flushed before draw
public boolean dirtyColorAttrib = true;
public boolean dirtyNormalAttrib;
public boolean dirtyTexCoordAttrib;
public boolean dirtyLightmapAttrib = true;
public boolean unit23TexCoordSetDuringDraw = false;
public int maxBoundTextureUnit = 0;
public int maxBoundImageUnit = 0;
public final IntStack attribs = new IntArrayList(GLStateManager.MAX_ATTRIB_STACK_DEPTH);
public int attribDepth = 0;
@SuppressWarnings("unchecked")
public final List<IStateStack<?>>[] modifiedAtDepth = new List[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedMvGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedMvLinearGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedProjGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedTexMatGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedLightingGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedFragmentGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedColorGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedNormalGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final int[] savedTexCoordGen = new int[GLStateManager.MAX_ATTRIB_STACK_DEPTH];
public final IntegerStateStack activeTextureUnit = new IntegerStateStack(0);
public int clientActiveTextureUnit = 0;
public final IntegerStateStack shadeModelState = new IntegerStateStack(GL11.GL_SMOOTH);
public final TextureUnitArray textures = new TextureUnitArray();
public final ImageUnitArray imageUnits = new ImageUnitArray();
public final BlendStateStack blendState = new BlendStateStack();
public final BooleanStateStack blendMode = track(new BooleanStateStack(GL11.GL_BLEND));
public final BooleanStateStack scissorTest = track(new BooleanStateStack(GL11.GL_SCISSOR_TEST));
public final DepthStateStack depthState = new DepthStateStack();
public final BooleanStateStack depthTest = track(new BooleanStateStack(GL11.GL_DEPTH_TEST));
public final FogStateStack fogState = new FogStateStack();
public final BooleanStateStack fogMode = track(new BooleanStateStack(GL11.GL_FOG, false, true));
public final Color4Stack color = new Color4Stack();
public final Color4Stack clearColor = new Color4Stack(new Color4(0.0F, 0.0F, 0.0F, 0.0F));
public final ColorMaskStack colorMask = new ColorMaskStack();
public final IntegerStateStack drawBuffer = new IntegerStateStack(GLStateManager.DEFAULT_DRAW_BUFFER);
public final IntegerStateStack logicOpMode = new IntegerStateStack(GL11.GL_COPY);
public final BooleanStateStack cullState = track(new BooleanStateStack(GL11.GL_CULL_FACE));
public final AlphaStateStack alphaState = new AlphaStateStack();
public final BooleanStateStack alphaTest = track(new BooleanStateStack(GL11.GL_ALPHA_TEST, false, true));
public final BooleanStateStack lightingState = track(new BooleanStateStack(GL11.GL_LIGHTING, false, true));
public final BooleanStateStack rescaleNormalState = track(new BooleanStateStack(GL12.GL_RESCALE_NORMAL, false, true));
public final BooleanStateStack normalizeState = track(new BooleanStateStack(GL11.GL_NORMALIZE, false, true));
public final BooleanStateStack ditherState = track(new BooleanStateStack(GL11.GL_DITHER, true));
public final BooleanStateStack stencilTest = track(new BooleanStateStack(GL11.GL_STENCIL_TEST));
public final BooleanStateStack lineSmoothState = track(new BooleanStateStack(GL11.GL_LINE_SMOOTH));
public final BooleanStateStack lineStippleState = track(new BooleanStateStack(GL11.GL_LINE_STIPPLE, false, true));
public final BooleanStateStack pointSmoothState = track(new BooleanStateStack(GL11.GL_POINT_SMOOTH, false, true));
public final BooleanStateStack polygonSmoothState = track(new BooleanStateStack(GL11.GL_POLYGON_SMOOTH));
public final BooleanStateStack polygonStippleState = track(new BooleanStateStack(GL11.GL_POLYGON_STIPPLE, false, true));
public final BooleanStateStack multisampleState = track(new BooleanStateStack(GL13.GL_MULTISAMPLE, true));
public final BooleanStateStack sampleAlphaToCoverageState = track(new BooleanStateStack(GL13.GL_SAMPLE_ALPHA_TO_COVERAGE));
public final BooleanStateStack sampleAlphaToOneState = track(new BooleanStateStack(GL13.GL_SAMPLE_ALPHA_TO_ONE));
public final BooleanStateStack sampleCoverageState = track(new BooleanStateStack(GL13.GL_SAMPLE_COVERAGE));
public final BooleanStateStack colorLogicOpState = track(new BooleanStateStack(GL11.GL_COLOR_LOGIC_OP));
public final BooleanStateStack indexLogicOpState = track(new BooleanStateStack(GL11.GL_INDEX_LOGIC_OP, false, true));
public final BooleanStateStack polygonOffsetPointState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_POINT));
public final BooleanStateStack polygonOffsetLineState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_LINE));
public final BooleanStateStack polygonOffsetFillState = track(new BooleanStateStack(GL11.GL_POLYGON_OFFSET_FILL));
public final LineStateStack lineState = new LineStateStack();
public final PointStateStack pointState = new PointStateStack();
public final PolygonStateStack polygonState = new PolygonStateStack();
public final StencilStateStack stencilState = new StencilStateStack();
public int stencilBitMask = 0xFFFFFFFF;
public final BooleanStateStack autoNormalState = track(new BooleanStateStack(GL11.GL_AUTO_NORMAL, false, true));
public final BooleanStateStack map1Color4State = track(new BooleanStateStack(GL11.GL_MAP1_COLOR_4, false, true));
public final BooleanStateStack map1IndexState = track(new BooleanStateStack(GL11.GL_MAP1_INDEX, false, true));
public final BooleanStateStack map1NormalState = track(new BooleanStateStack(GL11.GL_MAP1_NORMAL, false, true));
public final BooleanStateStack map1TextureCoord1State = track(new BooleanStateStack(GL11.GL_MAP1_TEXTURE_COORD_1, false, true));
public final BooleanStateStack map1TextureCoord2State = track(new BooleanStateStack(GL11.GL_MAP1_TEXTURE_COORD_2, false, true));
public final BooleanStateStack map1TextureCoord3State = track(new BooleanStateStack(GL11.GL_MAP1_TEXTURE_COORD_3, false, true));
public final BooleanStateStack map1TextureCoord4State = track(new BooleanStateStack(GL11.GL_MAP1_TEXTURE_COORD_4, false, true));
public final BooleanStateStack map1Vertex3State = track(new BooleanStateStack(GL11.GL_MAP1_VERTEX_3, false, true));
public final BooleanStateStack map1Vertex4State = track(new BooleanStateStack(GL11.GL_MAP1_VERTEX_4, false, true));
public final BooleanStateStack map2Color4State = track(new BooleanStateStack(GL11.GL_MAP2_COLOR_4, false, true));
public final BooleanStateStack map2IndexState = track(new BooleanStateStack(GL11.GL_MAP2_INDEX, false, true));
public final BooleanStateStack map2NormalState = track(new BooleanStateStack(GL11.GL_MAP2_NORMAL, false, true));
public final BooleanStateStack map2TextureCoord1State = track(new BooleanStateStack(GL11.GL_MAP2_TEXTURE_COORD_1, false, true));
public final BooleanStateStack map2TextureCoord2State = track(new BooleanStateStack(GL11.GL_MAP2_TEXTURE_COORD_2, false, true));
public final BooleanStateStack map2TextureCoord3State = track(new BooleanStateStack(GL11.GL_MAP2_TEXTURE_COORD_3, false, true));
public final BooleanStateStack map2TextureCoord4State = track(new BooleanStateStack(GL11.GL_MAP2_TEXTURE_COORD_4, false, true));
public final BooleanStateStack map2Vertex3State = track(new BooleanStateStack(GL11.GL_MAP2_VERTEX_3, false, true));
public final BooleanStateStack map2Vertex4State = track(new BooleanStateStack(GL11.GL_MAP2_VERTEX_4, false, true));
public final BooleanStateStack[] clipPlaneStates = new BooleanStateStack[GLStateManager.MAX_CLIP_PLANES];
public final ClipPlaneState clipPlaneState = new ClipPlaneState();
public final FloatBuffer queryScratch = BufferUtils.createFloatBuffer(16);
public final DoubleBuffer queryScratchDouble = BufferUtils.createDoubleBuffer(16);
public final MatrixModeStack matrixMode = new MatrixModeStack();
public final Matrix4fStack modelViewMatrix = new Matrix4fStack(GLStateManager.MAX_MODELVIEW_STACK_DEPTH);
public final Matrix4fStack projectionMatrix = new Matrix4fStack(GLStateManager.MAX_PROJECTION_STACK_DEPTH);
public final BooleanStateStack[] lightStates = new BooleanStateStack[8];
public final LightStateStack[] lightDataStates = new LightStateStack[8];
public final BooleanStateStack colorMaterial = track(new BooleanStateStack(GL11.GL_COLOR_MATERIAL, false, true));
public final IntegerStateStack colorMaterialFace = new IntegerStateStack(GL11.GL_FRONT_AND_BACK);
public final IntegerStateStack colorMaterialParameter = new IntegerStateStack(GL11.GL_AMBIENT_AND_DIFFUSE);
public final LightModelStateStack lightModel = new LightModelStateStack();
public final MaterialStateStack frontMaterial = new MaterialStateStack(GL11.GL_FRONT);
public final MaterialStateStack backMaterial = new MaterialStateStack(GL11.GL_BACK);
public final ViewPortStateStack viewportState = new ViewPortStateStack();
public int activeProgram = 0;
public int listBase = 0;
public int boundVBO;
public int boundVAO;
public int boundPixelUnpackBuffer;
public int boundPixelPackBuffer;
public PixelUnpackState pixelUnpackState = PixelUnpackState.DEFAULT;
public final int[] clientAttribSavedTextureUnit = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public final int[] clientAttribSavedVertexFlags = new int[GLStateManager.CLIENT_ATTRIB_STACK_DEPTH];
public int clientAttribStackPointer = 0;
public final boolean[] restoreUnitChanged = new boolean[GLStateManager.MAX_TEXTURE_UNITS];
public boolean restoreDepthChanged, restoreBlendChanged, restoreColorMaskChanged, restoreClearColorChanged, restoreDrawBufferChanged, restoreLogicOpChanged, restoreStencilChanged, restoreViewportChanged, restoreLineChanged, restorePointChanged, restorePolygonChanged, restoreActiveUnitChanged;
public int drawFramebuffer = 0;
public int readFramebuffer = 0;
public int texGenGeneration;
GLContextState() {
for (int i = 0; i < GLStateManager.MAX_ATTRIB_STACK_DEPTH; i++) modifiedAtDepth[i] = new ArrayList<>();
for (int i = 0; i < lightStates.length; i++) {
lightStates[i] = track(new BooleanStateStack(GL11.GL_LIGHT0 + i, false, true));
}
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
clipPlaneStates[i] = track(new BooleanStateStack(GL11.GL_CLIP_PLANE0 + i));
}
}
void init() {
for (int i = 0; i < lightDataStates.length; i++) {
lightDataStates[i] = new LightStateStack(GL11.GL_LIGHT0 + i);
}
}
}
@@ -6,6 +6,8 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.backend.GLDebugMessageListener;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMInitConfig;
import org.lwjgl.opengl.EXTBlendColor;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
@@ -14,6 +16,10 @@ import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL43;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public final class GLDebug {
/**
@@ -252,25 +258,6 @@ public final class GLDebug {
};
}
public static String getDataTypeName(int type) {
return switch (type) {
case GL11.GL_UNSIGNED_BYTE -> "UNSIGNED_BYTE";
case GL11.GL_BYTE -> "BYTE";
case GL11.GL_UNSIGNED_SHORT -> "UNSIGNED_SHORT";
case GL11.GL_SHORT -> "SHORT";
case GL11.GL_UNSIGNED_INT -> "UNSIGNED_INT";
case GL11.GL_INT -> "INT";
case GL11.GL_FLOAT -> "FLOAT";
case GL11.GL_DOUBLE -> "DOUBLE";
case GL12.GL_UNSIGNED_BYTE_3_3_2 -> "UNSIGNED_BYTE_3_3_2";
case GL12.GL_UNSIGNED_SHORT_4_4_4_4 -> "UNSIGNED_SHORT_4_4_4_4";
case GL12.GL_UNSIGNED_SHORT_5_5_5_1 -> "UNSIGNED_SHORT_5_5_5_1";
case GL12.GL_UNSIGNED_INT_8_8_8_8 -> "UNSIGNED_INT_8_8_8_8";
case GL12.GL_UNSIGNED_INT_10_10_10_2 -> "UNSIGNED_INT_10_10_10_2";
default -> String.format("0x%X", type);
};
}
public static String getLightName(int light) {
return switch (light) {
case GL11.GL_LIGHT0 -> "LIGHT0";
@@ -559,7 +546,9 @@ public final class GLDebug {
}
public static void initDebugState() {
if (BackendManager.RENDER_BACKEND.supportsDebugOutput()) {
final GLSMInitConfig cfg = GLStateManager.getInitConfig();
final boolean userRequested = cfg != null && (cfg.isLwjglDebug() || CaptureGate.enabledAtStartup());
if (userRequested && BackendManager.RENDER_BACKEND.supportsDebugOutput()) {
debugState = new KHRDebugState();
} else {
debugState = new UnsupportedDebugState();
@@ -572,6 +561,19 @@ public final class GLDebug {
}
}
/**
* Overload that lets you gate the String concat behind the debugState
*/
public static void pushGroup(String prefix, int value) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.pushGroup(prefix + value);
}
}
public static boolean isActive() {
return debugState instanceof KHRDebugState && CaptureGate.markersThisFrame && Thread.currentThread() == GLStateManager.getMainThread();
}
public static void pushGroup(String group) {
if (debugState != null && Thread.currentThread() == GLStateManager.getMainThread()) {
debugState.pushGroup(group);
@@ -597,4 +599,49 @@ public final class GLDebug {
return "";
}
private static final Scope NOOP_SCOPE = () -> {};
private static final Scope ACTIVE_SCOPE = GLDebug::popGroup;
public static Scope scope(String name) {
if (isActive()) {
debugState.pushGroup(name);
return ACTIVE_SCOPE;
}
return NOOP_SCOPE;
}
@FunctionalInterface
public interface Scope extends AutoCloseable {
@Override void close();
}
private static MethodHandle khrCallbackInvoke;
private static boolean khrCallbackInvokeResolved;
/// Use reflection because of lwjgl3ify
static GLDebugMessageListener adaptDebugCallback(Object callback) {
if (callback == null) return null;
final MethodHandle invoke = resolveKhrCallbackInvoke(callback.getClass());
if (invoke == null) return null;
return (source, type, id, severity, length, message, userParam) -> {
try {
invoke.invoke(callback, source, type, id, severity, length, message, userParam);
} catch (Throwable t) {
GLStateManager.LOGGER.warn("Debug message callback threw", t);
}
};
}
private static synchronized MethodHandle resolveKhrCallbackInvoke(Class<?> callbackClass) {
if (!khrCallbackInvokeResolved) {
khrCallbackInvokeResolved = true;
try {
khrCallbackInvoke = MethodHandles.publicLookup().findVirtual(callbackClass, "invoke", MethodType.methodType(void.class, int.class, int.class, int.class, int.class, int.class, long.class, long.class));
} catch (ReflectiveOperationException e) {
GLStateManager.LOGGER.warn("{} has no public invoke(int,int,int,int,int,long,long); debug messages registered through it will not be delivered.", callbackClass.getName());
}
}
return khrCallbackInvoke;
}
}
@@ -0,0 +1,34 @@
package com.gtnewhorizons.angelica.glsm;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL13;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL32;
public final class GLESCaps {
private GLESCaps() {}
public static boolean isCapAllowed(int cap, boolean isGLES, boolean hasClipCullDistance) {
if (!isGLES) return true;
if (cap >= GL11.GL_CLIP_PLANE0 && cap < GL11.GL_CLIP_PLANE0 + GLStateManager.MAX_CLIP_PLANES) {
return hasClipCullDistance;
}
return switch (cap) {
case GL11.GL_LINE_SMOOTH,
GL11.GL_POLYGON_SMOOTH,
GL11.GL_COLOR_LOGIC_OP,
GL11.GL_POLYGON_OFFSET_POINT,
GL11.GL_POLYGON_OFFSET_LINE,
GL13.GL_MULTISAMPLE,
GL13.GL_SAMPLE_ALPHA_TO_ONE,
GL20.GL_VERTEX_PROGRAM_POINT_SIZE,
GL30.GL_FRAMEBUFFER_SRGB,
GL31.GL_PRIMITIVE_RESTART,
GL32.GL_DEPTH_CLAMP -> false;
default -> true;
};
}
}
@@ -0,0 +1,86 @@
package com.gtnewhorizons.angelica.glsm;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL14;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
public final class GLESFormatRemap {
private GLESFormatRemap() {}
public record Result(int internalFormat, int format, int type) {}
public static Result apply(int internalformat, int format, int type, boolean isGLES) {
internalformat = promoteAlphaFormat(internalformat);
if (isGLES) {
internalformat = remapInternalFormat(internalformat);
if (type == GL12.GL_UNSIGNED_INT_8_8_8_8_REV && (format == GL12.GL_BGRA || format == GL11.GL_RGBA)) {
type = GL11.GL_UNSIGNED_BYTE;
}
if (isGenericPixelType(type)) {
type = typeForInternalFormatES32(internalformat, type);
}
}
return new Result(internalformat, format, type);
}
public static int promoteAlphaFormat(int internalformat) {
return switch (internalformat) {
case GL11.GL_ALPHA4 -> GL11.GL_RGBA4;
case GL11.GL_ALPHA8 -> GL11.GL_RGBA8;
case GL11.GL_ALPHA12 -> GL11.GL_RGBA12;
case GL11.GL_ALPHA16 -> GL11.GL_RGBA16;
default -> internalformat;
};
}
public static int remapInternalFormat(int internalformat) {
return switch (internalformat) {
case GL11.GL_RGB16, GL11.GL_RGB12 -> GL30.GL_RGB16F;
case GL11.GL_RGBA16, GL11.GL_RGBA12 -> GL30.GL_RGBA16F;
case GL30.GL_R16 -> GL30.GL_R16F;
case GL30.GL_RG16 -> GL30.GL_RG16F;
case GL11.GL_RGB10, GL11.GL_R3_G3_B2 -> GL11.GL_RGB8;
default -> internalformat;
};
}
public static int remapPixelType(int format, int type) {
if (type == GL12.GL_UNSIGNED_INT_8_8_8_8_REV && (format == GL12.GL_BGRA || format == GL11.GL_RGBA)) {
return GL11.GL_UNSIGNED_BYTE;
}
return type;
}
public static int typeForInternalFormatES32(int internalformat, int type) {
return switch (internalformat) {
case GL30.GL_R16F, GL30.GL_RG16F, GL30.GL_RGB16F, GL30.GL_RGBA16F -> GL30.GL_HALF_FLOAT;
case GL30.GL_R32F, GL30.GL_RG32F, GL30.GL_RGB32F, GL30.GL_RGBA32F -> GL11.GL_FLOAT;
case GL31.GL_R8_SNORM, GL31.GL_RG8_SNORM, GL31.GL_RGB8_SNORM, GL31.GL_RGBA8_SNORM -> GL11.GL_BYTE;
case GL30.GL_R11F_G11F_B10F, GL30.GL_RGB9_E5 -> GL30.GL_HALF_FLOAT;
case GL11.GL_RGB10_A2 -> GL12.GL_UNSIGNED_INT_2_10_10_10_REV;
case GL30.GL_R8I, GL30.GL_RG8I, GL30.GL_RGB8I, GL30.GL_RGBA8I,
GL30.GL_R16I, GL30.GL_RG16I, GL30.GL_RGB16I, GL30.GL_RGBA16I,
GL30.GL_R32I, GL30.GL_RG32I, GL30.GL_RGB32I, GL30.GL_RGBA32I -> GL11.GL_INT;
case GL30.GL_R8UI, GL30.GL_RG8UI, GL30.GL_RGB8UI, GL30.GL_RGBA8UI,
GL30.GL_R16UI, GL30.GL_RG16UI, GL30.GL_RGB16UI, GL30.GL_RGBA16UI,
GL30.GL_R32UI, GL30.GL_RG32UI, GL30.GL_RGB32UI, GL30.GL_RGBA32UI -> GL11.GL_UNSIGNED_INT;
case GL14.GL_DEPTH_COMPONENT24 -> GL11.GL_UNSIGNED_INT;
case GL30.GL_DEPTH_COMPONENT32F -> GL11.GL_FLOAT;
case GL30.GL_DEPTH24_STENCIL8 -> GL30.GL_UNSIGNED_INT_24_8;
case GL30.GL_DEPTH32F_STENCIL8 -> GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
default -> type;
};
}
public static boolean isGenericPixelType(int type) {
return type == GL11.GL_UNSIGNED_BYTE
|| type == GL11.GL_UNSIGNED_SHORT
|| type == GL11.GL_UNSIGNED_INT
|| type == GL11.GL_BYTE
|| type == GL11.GL_SHORT
|| type == GL11.GL_INT;
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,57 @@
package com.gtnewhorizons.angelica.glsm;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL30;
public final class GLTypes {
private GLTypes() {}
public static int sizeBytes(int glType) {
return switch (glType) {
case GL11.GL_BYTE, GL11.GL_UNSIGNED_BYTE -> 1;
case GL11.GL_SHORT, GL11.GL_UNSIGNED_SHORT, GL30.GL_HALF_FLOAT -> 2;
case GL11.GL_INT, GL11.GL_UNSIGNED_INT, GL11.GL_FLOAT -> 4;
case GL11.GL_DOUBLE -> 8;
default -> throw new IllegalArgumentException("Unknown GL type: 0x" + Integer.toHexString(glType));
};
}
public static int packedTexelBytes(int glType) {
return switch (glType) {
case GL12.GL_UNSIGNED_BYTE_3_3_2, GL12.GL_UNSIGNED_BYTE_2_3_3_REV -> 1;
case GL12.GL_UNSIGNED_SHORT_5_6_5, GL12.GL_UNSIGNED_SHORT_5_6_5_REV,
GL12.GL_UNSIGNED_SHORT_4_4_4_4, GL12.GL_UNSIGNED_SHORT_4_4_4_4_REV,
GL12.GL_UNSIGNED_SHORT_5_5_5_1, GL12.GL_UNSIGNED_SHORT_1_5_5_5_REV -> 2;
case GL12.GL_UNSIGNED_INT_8_8_8_8, GL12.GL_UNSIGNED_INT_8_8_8_8_REV,
GL12.GL_UNSIGNED_INT_10_10_10_2, GL12.GL_UNSIGNED_INT_2_10_10_10_REV,
GL30.GL_UNSIGNED_INT_24_8, GL30.GL_UNSIGNED_INT_10F_11F_11F_REV,
GL30.GL_UNSIGNED_INT_5_9_9_9_REV -> 4;
case GL30.GL_FLOAT_32_UNSIGNED_INT_24_8_REV -> 8;
default -> 0;
};
}
public static String name(int glType) {
return switch (glType) {
case GL11.GL_BYTE -> "BYTE";
case GL11.GL_UNSIGNED_BYTE -> "UNSIGNED_BYTE";
case GL11.GL_SHORT -> "SHORT";
case GL11.GL_UNSIGNED_SHORT -> "UNSIGNED_SHORT";
case GL11.GL_INT -> "INT";
case GL11.GL_UNSIGNED_INT -> "UNSIGNED_INT";
case GL11.GL_FLOAT -> "FLOAT";
case GL11.GL_DOUBLE -> "DOUBLE";
case GL30.GL_HALF_FLOAT -> "HALF_FLOAT";
case GL12.GL_UNSIGNED_BYTE_3_3_2 -> "UNSIGNED_BYTE_3_3_2";
case GL12.GL_UNSIGNED_SHORT_4_4_4_4 -> "UNSIGNED_SHORT_4_4_4_4";
case GL12.GL_UNSIGNED_SHORT_5_5_5_1 -> "UNSIGNED_SHORT_5_5_5_1";
case GL12.GL_UNSIGNED_INT_8_8_8_8 -> "UNSIGNED_INT_8_8_8_8";
case GL12.GL_UNSIGNED_INT_8_8_8_8_REV -> "UNSIGNED_INT_8_8_8_8_REV";
case GL12.GL_UNSIGNED_INT_10_10_10_2 -> "UNSIGNED_INT_10_10_10_2";
case GL12.GL_UNSIGNED_INT_2_10_10_10_REV -> "UNSIGNED_INT_2_10_10_10_REV";
default -> String.format("0x%X", glType);
};
}
}
@@ -1,10 +1,19 @@
package com.gtnewhorizons.angelica.glsm;
import org.antlr.v4.runtime.BufferedTokenStream;
import org.antlr.v4.runtime.CharStreams;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.Token;
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.grammar.GLSLPreParser;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -36,8 +45,12 @@ public class GlslTransformUtils {
/** Reserved words added in later GLSL versions that may appear as identifiers in older shaders. */
private record ReservedWordRename(Pattern pattern, String replacement) {}
private static final Map<Integer, List<ReservedWordRename>> VERSIONED_RESERVED_WORDS = Map.of(
400, List.of(
// Some reserved words to always rename
0, List.of(
new ReservedWordRename(Pattern.compile("\\bsample\\b"), RENAMED_PREFIX + "sample"),
new ReservedWordRename(Pattern.compile("\\bnew\\b"), RENAMED_PREFIX + "new")
),
400, List.of(
new ReservedWordRename(Pattern.compile("\\bsampler\\b(?!\\d)"), RENAMED_PREFIX + "sampler")
)
);
@@ -75,6 +88,77 @@ public class GlslTransformUtils {
return source;
}
public record QuietParse(GLSLParser.Translation_unitContext full, GLSLPreParser.Translation_unitContext pre) {}
public static GLSLParser.Translation_unitContext parseFullQuiet(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final GLSLParser parser = new GLSLParser(new CommonTokenStream(lexer));
parser.removeErrorListeners();
parser.setBuildParseTree(true);
return parser.translation_unit();
}
public static GLSLPreParser.Translation_unitContext parsePreQuiet(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final GLSLPreParser preParser = new GLSLPreParser(new CommonTokenStream(lexer, GLSLLexer.DIRECTIVES));
preParser.removeErrorListeners();
preParser.setBuildParseTree(true);
return preParser.translation_unit();
}
public static QuietParse parseBothQuiet(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final GLSLPreParser preParser = new GLSLPreParser(new BufferedTokenStream(lexer));
preParser.removeErrorListeners();
preParser.setBuildParseTree(true);
final GLSLPreParser.Translation_unitContext pre = preParser.translation_unit();
lexer.reset();
final GLSLParser parser = new GLSLParser(new CommonTokenStream(lexer));
parser.removeErrorListeners();
parser.setBuildParseTree(true);
return new QuietParse(parser.translation_unit(), pre);
}
public static Set<String> identifiersInDirectiveText(String source) {
final GLSLLexer lexer = new GLSLLexer(CharStreams.fromString(source));
lexer.removeErrorListeners();
final BufferedTokenStream tokens = new BufferedTokenStream(lexer);
tokens.fill();
final Set<String> names = new HashSet<>();
for (Token token : tokens.getTokens()) {
switch (token.getType()) {
case GLSLLexer.PROGRAM_TEXT, GLSLLexer.MACRO_TEXT, GLSLLexer.CONSTANT_EXPRESSION -> collectIdentifiers(token.getText(), names);
default -> { }
}
}
return names;
}
private static void collectIdentifiers(String text, Set<String> out) {
if (text == null) return;
final int len = text.length();
int i = 0;
while (i < len) {
final char c = text.charAt(i);
if (c != '_' && !Character.isLetter(c)) {
i++;
continue;
}
int end = i + 1;
while (end < len) {
final char n = text.charAt(end);
if (n != '_' && !Character.isLetterOrDigit(n)) break;
end++;
}
out.add(text.substring(i, end));
i = end;
}
}
public static String getFormattedShader(ParseTree tree, String header) {
final StringBuilder sb = new StringBuilder(header + "\n");
final String[] tabHolder = {""};
@@ -0,0 +1,34 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.glsm.states.PixelUnpackState;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import java.nio.ByteBuffer;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
public final class LTWWorkaround {
private LTWWorkaround() {}
private static ByteBuffer oneTexel;
public static boolean isLtwVersionString(String glVersion) {
return glVersion != null && glVersion.contains("LTW");
}
public static void onTexImage2D(int target, int level, int format, boolean hasPixels) {
if (target != GL11.GL_TEXTURE_2D || level != 0 || format != GL12.GL_BGRA || hasPixels) return;
if (oneTexel == null) {
oneTexel = BufferUtils.createByteBuffer(4);
}
oneTexel.clear();
GLStateManager.forcePixelUnpackState(PixelUnpackState.DEFAULT);
GLStateManager.suspendPixelUnpackBuffer();
RENDER_BACKEND.texSubImage2D(target, level, 0, 0, 1, 1, GL11.GL_RGBA, GL11.GL_UNSIGNED_BYTE, oneTexel);
GLStateManager.restorePixelUnpackBuffer();
GLStateManager.restorePixelUnpackState(PixelUnpackState.DEFAULT);
}
}
@@ -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,54 @@ 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";
drawSharedQuadEbo(first, vertexCount, 1, false);
}
public static void drawQuadsAsTrianglesInstanced(int first, int vertexCount, int primcount) {
drawSharedQuadEbo(first, vertexCount, primcount, true);
}
private static int misalignedDraws;
private static void drawSharedQuadEbo(int first, int vertexCount, int primcount, boolean instanced) {
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);
if ((first & 3) != 0) {
misalignedDraws++;
if ((misalignedDraws & (misalignedDraws - 1)) == 0) {
GLStateManager.LOGGER.warn("QuadConverter: quad draw with unaligned first={} count={} (#{}) -- using scratch indices", first, vertexCount, misalignedDraws);
}
final ByteBuffer dst = memAlloc(quadCount * 6 * 4);
long ptr = memAddress0(dst);
for (int i = 0; i < quadCount; i++) {
final int base = first + i * 4;
memPutInt(ptr, base);
memPutInt(ptr + 4, base + 1);
memPutInt(ptr + 8, base + 3);
memPutInt(ptr + 12, base + 1);
memPutInt(ptr + 16, base + 2);
memPutInt(ptr + 20, base + 3);
ptr += 24;
}
uploadAndDraw(dst, quadCount * 6, GL11.GL_UNSIGNED_INT, 4, primcount, instanced);
return;
}
final boolean locked = GLStateManager.acquireDrawLock();
try {
GLStateManager.preDraw();
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 +144,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,52 +191,29 @@ 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 = GLTypes.sizeBytes(type);
// Read source indices from caller's EBO
final ByteBuffer src = memAlloc(indexCount * bytesPerIndex);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, src);
final long srcAddr = memAddress0(src);
// Allocate output as GL_UNSIGNED_INT
final ByteBuffer dst = memAlloc(triIndexCount * 4);
final long dstAddr = memAddress0(dst);
for (int i = 0; i < quadCount; i++) {
final int a, b, c, d;
switch (type) {
case GL11.GL_UNSIGNED_INT -> {
final long p = srcAddr + (long) i * 16;
a = memGetInt(p); b = memGetInt(p + 4); c = memGetInt(p + 8); d = memGetInt(p + 12);
}
case GL11.GL_UNSIGNED_SHORT -> {
final long p = srcAddr + (long) i * 8;
a = memGetShort(p) & 0xFFFF; b = memGetShort(p + 2) & 0xFFFF;
c = memGetShort(p + 4) & 0xFFFF; d = memGetShort(p + 6) & 0xFFFF;
}
default /* GL_UNSIGNED_BYTE */ -> {
final long p = srcAddr + (long) i * 4;
a = memGetByte(p) & 0xFF; b = memGetByte(p + 1) & 0xFF;
c = memGetByte(p + 2) & 0xFF; d = memGetByte(p + 3) & 0xFF;
}
}
// GL_LAST_VERTEX_CONVENTION: (a,b,d), (b,c,d)
final long o = (long) i * 24;
memPutInt(dstAddr + o, a);
memPutInt(dstAddr + o + 4, b);
memPutInt(dstAddr + o + 8, d);
memPutInt(dstAddr + o + 12, b);
memPutInt(dstAddr + o + 16, c);
memPutInt(dstAddr + o + 20, d);
}
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);
}
/**
@@ -198,16 +227,8 @@ public final class QuadConverter {
final int triIndexCount = quadCount * 6;
final ByteBuffer dst = memAlloc(triIndexCount * 4);
final long dstAddr = memAddress0(dst);
final int basePos = indices.position();
for (int i = 0; i < quadCount; i++) {
final int base = basePos + i * 4;
final int a = indices.get(base), b = indices.get(base + 1), c = indices.get(base + 2), d = indices.get(base + 3);
final long o = (long) i * 24;
memPutInt(dstAddr + o, a); memPutInt(dstAddr + o + 4, b); memPutInt(dstAddr + o + 8, d);
memPutInt(dstAddr + o + 12, b); memPutInt(dstAddr + o + 16, c); memPutInt(dstAddr + o + 20, d);
}
final long srcAddr = memAddress0(indices) + (long) indices.position() * 4;
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_INT, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
@@ -223,16 +244,8 @@ public final class QuadConverter {
final int triIndexCount = quadCount * 6;
final ByteBuffer dst = memAlloc(triIndexCount * 2);
final long dstAddr = memAddress0(dst);
final int basePos = indices.position();
for (int i = 0; i < quadCount; i++) {
final int base = basePos + i * 4;
final short a = indices.get(base), b = indices.get(base + 1), c = indices.get(base + 2), d = indices.get(base + 3);
final long o = (long) i * 12;
memPutShort(dstAddr + o, a); memPutShort(dstAddr + o + 2, b); memPutShort(dstAddr + o + 4, d);
memPutShort(dstAddr + o + 6, b); memPutShort(dstAddr + o + 8, c); memPutShort(dstAddr + o + 10, d);
}
final long srcAddr = memAddress0(indices) + (long) indices.position() * 2;
triangulateQuads(srcAddr, GL11.GL_UNSIGNED_SHORT, memAddress0(dst), GL11.GL_UNSIGNED_SHORT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_SHORT, 2);
}
@@ -248,31 +261,7 @@ public final class QuadConverter {
final long srcAddr = memAddress0(indices) + indices.position();
final ByteBuffer dst = memAlloc(triIndexCount * 4);
final long dstAddr = memAddress0(dst);
for (int i = 0; i < quadCount; i++) {
final int a, b, c, d;
switch (type) {
case GL11.GL_UNSIGNED_INT -> {
final long p = srcAddr + (long) i * 16;
a = memGetInt(p); b = memGetInt(p + 4); c = memGetInt(p + 8); d = memGetInt(p + 12);
}
case GL11.GL_UNSIGNED_SHORT -> {
final long p = srcAddr + (long) i * 8;
a = memGetShort(p) & 0xFFFF; b = memGetShort(p + 2) & 0xFFFF;
c = memGetShort(p + 4) & 0xFFFF; d = memGetShort(p + 6) & 0xFFFF;
}
case GL11.GL_UNSIGNED_BYTE -> {
final long p = srcAddr + (long) i * 4;
a = memGetByte(p) & 0xFF; b = memGetByte(p + 1) & 0xFF;
c = memGetByte(p + 2) & 0xFF; d = memGetByte(p + 3) & 0xFF;
}
default -> { memFree(dst); return; }
}
final long o = (long) i * 24;
memPutInt(dstAddr + o, a); memPutInt(dstAddr + o + 4, b); memPutInt(dstAddr + o + 8, d);
memPutInt(dstAddr + o + 12, b); memPutInt(dstAddr + o + 16, c); memPutInt(dstAddr + o + 20, d);
}
triangulateQuads(srcAddr, type, memAddress0(dst), GL11.GL_UNSIGNED_INT, quadCount);
uploadAndDraw(dst, triIndexCount, GL11.GL_UNSIGNED_INT, 4);
}
@@ -282,14 +271,140 @@ public final class QuadConverter {
*/
public static void destroy() {
if (eboId != 0) {
RENDER_BACKEND.deleteBuffers(eboId);
GLStateManager.glDeleteBuffers(eboId);
eboId = 0;
maxQuads = 0;
}
if (scratchEboId != 0) {
RENDER_BACKEND.deleteBuffers(scratchEboId);
GLStateManager.glDeleteBuffers(scratchEboId);
scratchEboId = 0;
scratchEboCapacity = 0;
}
}
/**
* Read one index at {@code srcAddr + i * sizeof(srcType)} and return its
* zero-extended unsigned value.
*/
private static int readIndex(long srcAddr, int srcType, int i) {
return switch (srcType) {
case GL11.GL_UNSIGNED_BYTE -> memGetByte(srcAddr + i) & 0xFF;
case GL11.GL_UNSIGNED_SHORT -> memGetShort(srcAddr + i * 2L) & 0xFFFF;
case GL11.GL_UNSIGNED_INT -> memGetInt(srcAddr + i * 4L);
default -> throw new IllegalArgumentException("unsupported index type 0x" + Integer.toHexString(srcType));
};
}
/**
* Write one index at {@code dstAddr + i * sizeof(dstType)}.
*/
private static void writeIndex(long dstAddr, int dstType, int i, int value) {
switch (dstType) {
case GL11.GL_UNSIGNED_SHORT -> memPutShort(dstAddr + i * 2L, (short) value);
case GL11.GL_UNSIGNED_INT -> memPutInt(dstAddr + i * 4L, value);
default -> throw new IllegalArgumentException("unsupported index type 0x" + Integer.toHexString(dstType));
}
}
/**
* Read {@code quadCount} quads from {@code srcAddr} and emit 6 triangle indices
* per quad at {@code dstAddr} using {@code GL_LAST_VERTEX_CONVENTION}:
* {@code (a,b,d), (b,c,d)}. v3 is the provoking vertex for both triangles;
* diagonal runs b→d. Matches Mesa's {@code do_quad} LAST path.
*
* <p>Caller allocates dst of size {@code quadCount * 6 * sizeof(dstType)}.
*
* <p>Supported src types: {@code GL_UNSIGNED_BYTE} / {@code GL_UNSIGNED_SHORT}
* / {@code GL_UNSIGNED_INT}. Supported dst types: {@code GL_UNSIGNED_SHORT} /
* {@code GL_UNSIGNED_INT}. Widening only.
*/
public static void triangulateQuads(long srcAddr, int srcType, long dstAddr, int dstType, int quadCount) {
triangulateQuads(srcAddr, srcType, dstAddr, dstType, quadCount, 0);
}
/**
* Same as {@link #triangulateQuads(long, int, long, int, int)} but subtracts
* {@code minVtx} from every emitted index, rebasing to a 0-based buffer in
* one pass.
*/
public static void triangulateQuads(long srcAddr, int srcType, long dstAddr, int dstType, int quadCount, int minVtx) {
for (int i = 0; i < quadCount; i++) {
final int a = readIndex(srcAddr, srcType, i * 4) - minVtx;
final int b = readIndex(srcAddr, srcType, i * 4 + 1) - minVtx;
final int c = readIndex(srcAddr, srcType, i * 4 + 2) - minVtx;
final int d = readIndex(srcAddr, srcType, i * 4 + 3) - minVtx;
final int o = i * 6;
writeIndex(dstAddr, dstType, o, a);
writeIndex(dstAddr, dstType, o + 1, b);
writeIndex(dstAddr, dstType, o + 2, d);
writeIndex(dstAddr, dstType, o + 3, b);
writeIndex(dstAddr, dstType, o + 4, c);
writeIndex(dstAddr, dstType, o + 5, d);
}
}
/**
* Copy {@code count} indices from {@code srcAddr} to {@code dstAddr}, converting
* from {@code srcType} to {@code dstType}.
*
* <p>Supported src types: {@code GL_UNSIGNED_BYTE} / {@code GL_UNSIGNED_SHORT} /
* {@code GL_UNSIGNED_INT}. Supported dst types: {@code GL_UNSIGNED_SHORT} /
* {@code GL_UNSIGNED_INT}. Widening only; narrowing is rejected by
* {@link #writeIndex(long, int, int, int) writeIndex}.
*/
public static void widenIndices(long srcAddr, int srcType, long dstAddr, int dstType, int count) {
widenIndices(srcAddr, srcType, dstAddr, dstType, count, 0);
}
/**
* Same as {@link #widenIndices(long, int, long, int, int)} but subtracts
* {@code minVtx} from every written index in one pass.
*/
public static void widenIndices(long srcAddr, int srcType, long dstAddr, int dstType, int count, int minVtx) {
for (int i = 0; i < count; i++) {
writeIndex(dstAddr, dstType, i, readIndex(srcAddr, srcType, i) - minVtx);
}
}
/**
* Scan {@code count} indices at {@code srcAddr} of {@code srcType}. Returns
* {@code (max << 32) | (min & 0xFFFFFFFFL)} or {@code -1L} for an unsupported
* type or empty range.
*/
public static long scanMinMaxIndex(long srcAddr, int srcType, int count) {
if (count <= 0) return -1L;
long min = Long.MAX_VALUE;
long max = Long.MIN_VALUE;
switch (srcType) {
case GL11.GL_UNSIGNED_BYTE -> {
for (int i = 0; i < count; i++) {
final int v = memGetByte(srcAddr + i) & 0xFF;
if (v < min) min = v;
if (v > max) max = v;
}
}
case GL11.GL_UNSIGNED_SHORT -> {
for (int i = 0; i < count; i++) {
final int v = memGetShort(srcAddr + i * 2L) & 0xFFFF;
if (v < min) min = v;
if (v > max) max = v;
}
}
case GL11.GL_UNSIGNED_INT -> {
for (int i = 0; i < count; i++) {
final long v = memGetInt(srcAddr + i * 4L) & 0xFFFFFFFFL;
if (v < min) min = v;
if (v > max) max = v;
}
// readIndex / writeIndex / rebase all route UINT through signed int.
// Reject the > 2^31 slice so the caller bails rather than silently
// emitting negative indices. MC never hits this in practice.
if (max > Integer.MAX_VALUE) return -1L;
}
default -> {
return -1L;
}
}
return (max << 32) | (min & 0xFFFFFFFFL);
}
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.backend.BackendManager;
import com.gtnewhorizons.angelica.glsm.dsa.DSAARB;
import com.gtnewhorizons.angelica.glsm.dsa.DSAAccess;
@@ -8,6 +9,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;
@@ -17,6 +19,7 @@ import org.lwjgl.opengl.ARBShaderStorageBufferObject;
import org.lwjgl.opengl.EXTShaderImageLoadStore;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL42;
import org.lwjgl.opengl.GL43;
@@ -39,6 +42,7 @@ public class RenderSystem {
private static boolean supportsCompute;
private static boolean supportsImageLoadStore;
private static boolean supportsSSBO;
private static boolean supportsMultiDrawIndirect;
private static boolean supportsBufferStorage;
private static boolean supportsClearTexture;
private static boolean supportsTesselation;
@@ -47,6 +51,17 @@ 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;
private static volatile boolean isGLES;
private static volatile int glesVersion;
private static volatile boolean glesDetected;
private static volatile boolean hasClipCullDistance;
private static volatile boolean isLTW;
private static volatile boolean ltwDetected;
// Sampler object state tracking (null if unsupported)
private static int[] samplers;
@@ -60,24 +75,38 @@ public class RenderSystem {
public static void initRenderer() {
if (rendererInitialized) return;
rendererInitialized = true;
try {
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.");
}
} catch (NoSuchFieldError ignored) {
final boolean wasDetected = glesDetected;
if (isGLES() && !wasDetected) {
GLStateManager.LOGGER.info("OpenGL ES {}.{} detected", glesVersion / 100, (glesVersion / 10) % 10);
}
try {
if (dsaState == null && GLStateManager.capabilities.GL_ARB_direct_state_access) {
dsaState = new DSAARB();
GLStateManager.LOGGER.info("ARB_direct_state_access detected, enabling DSA.");
if (!isGLES) {
try {
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.");
}
} catch (NoSuchFieldError ignored) {
}
try {
if (dsaState == null && GLStateManager.capabilities.GL_ARB_direct_state_access) {
dsaState = new DSAARB();
GLStateManager.LOGGER.info("ARB_direct_state_access detected, enabling DSA.");
}
} catch (NoSuchFieldError ignored) {
}
} catch (NoSuchFieldError ignored) {
}
if (dsaState == null) {
dsaState = new DSAUnsupported();
GLStateManager.LOGGER.info("No DSA support detected, falling back to legacy OpenGL.");
GLStateManager.LOGGER.info("{}", isGLES ? "GLES context, using DSAUnsupported (bind-based) fallback." : "No DSA support detected, falling back to legacy OpenGL.");
}
BackendManager.init();
@@ -89,8 +118,10 @@ public class RenderSystem {
|| GLStateManager.capabilities.GL_ARB_shader_image_load_store
|| GLStateManager.capabilities.GL_EXT_shader_image_load_store;
supportsSSBO = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_shader_storage_buffer_object;
supportsBufferStorage = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_buffer_storage;
supportsMultiDrawIndirect = GLStateManager.capabilities.OpenGL43 || GLStateManager.capabilities.GL_ARB_multi_draw_indirect;
supportsBufferStorage = !isGLES && (GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_buffer_storage);
supportsClearTexture = GLStateManager.capabilities.OpenGL44 || GLStateManager.capabilities.GL_ARB_clear_texture;
hasClipCullDistance = !isGLES || hasExtension("GL_EXT_clip_cull_distance");
// Cache maximum image units
if (supportsImageLoadStore) {
@@ -130,13 +161,26 @@ public class RenderSystem {
GLStateManager.LOGGER.info("SSBO: {}, Max SSBO Bindings: {}", supportsSSBO, maxSSBOBindings);
GLStateManager.LOGGER.info("Buffer Storage: {}, Clear Texture: {}, Sampler Objects: {}", supportsBufferStorage, supportsClearTexture, supportsSamplerObjects);
if (GLStateManager.capabilities.OpenGL32) {
if (isGLES) {
GLStateManager.LOGGER.info("GL ES context detected, enabling shader transformer.");
ShaderManager.getInstance().enable();
} else 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) {
@@ -148,8 +192,14 @@ 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) {
final GLStateManager.GLESTexImageRemap remap = GLStateManager.remapTexImageForGLES(internalformat, format, type);
internalformat = remap.internalFormat();
format = remap.format();
type = remap.type();
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 +268,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 +326,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) {
@@ -291,12 +355,16 @@ public class RenderSystem {
return dsaState.getTexParameteri(texture, target, pname);
}
public static float getTexParameterf(int texture, int target, int pname) {
return dsaState.getTexParameterf(texture, target, pname);
}
public static int getTexLevelParameteri(int texture, int level, int pname) {
return dsaState.getTexLevelParameteri(texture, level, pname);
}
public static void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format) {
RENDER_BACKEND.bindImageTexture(unit, texture, level, layered, layer, access, format);
GLStateManager.glBindImageTexture(unit, texture, level, layered, layer, access, format);
}
public static int getMaxImageUnits() {
@@ -304,13 +372,69 @@ public class RenderSystem {
}
public static boolean supportsImageLoadStore() {
if (RENDER_BACKEND.isSDLGPU()) return true;
return supportsImageLoadStore;
}
public static boolean hasClipCullDistance() {
return hasClipCullDistance;
}
private static boolean hasExtension(String name) {
final int count = GLStateManager.glGetInteger(GL30.GL_NUM_EXTENSIONS);
for (int i = 0; i < count; i++) {
if (name.equals(GLStateManager.glGetStringi(GL11.GL_EXTENSIONS, i))) return true;
}
return false;
}
public static boolean isGLES() {
if (!glesDetected) detectGLES();
return isGLES;
}
private static synchronized void detectGLES() {
if (glesDetected) return;
try {
final String v = RENDER_BACKEND.getString(GL11.GL_VERSION);
if (v != null && v.startsWith("OpenGL ES ")) {
isGLES = true;
glesVersion = Integer.parseInt(parseGlVersionString(v));
}
glesDetected = true;
} catch (Throwable ignored) {
// No GL context on this thread yet (splash); retry on next call.
}
}
public static boolean isLTW() {
if (!ltwDetected) detectLTW();
return isLTW;
}
private static synchronized void detectLTW() {
if (ltwDetected) return;
if (SystemProperties.DISABLE_LTW_WORKAROUND) {
ltwDetected = true;
return;
}
try {
final String v = RENDER_BACKEND.getString(GL11.GL_VERSION);
if (v == null) return;
isLTW = LTWWorkaround.isLtwVersionString(v);
ltwDetected = true;
} catch (Throwable ignored) {
}
}
public static boolean supportsSSBO() {
return supportsSSBO;
}
public static boolean supportsMultiDrawIndirect() {
return supportsMultiDrawIndirect;
}
public static boolean supportsBufferStorage() {
return supportsBufferStorage;
}
@@ -391,9 +515,14 @@ public class RenderSystem {
GLStateManager.glMatrixMode(GL11.GL_MODELVIEW);
}
public static void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2,
int height2, int bufferChoice, int filter) {
dsaState.blitFramebuffer(source, dest, offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
public static void blitFramebuffer(int source, int dest, int offsetX, int offsetY, int width, int height, int offsetX2, int offsetY2, int width2, int height2, int bufferChoice, int filter) {
final boolean scissor = GLStateManager.glIsEnabled(GL11.GL_SCISSOR_TEST);
if (scissor) GLStateManager.glDisable(GL11.GL_SCISSOR_TEST);
try {
dsaState.blitFramebuffer(source, dest, offsetX, offsetY, width, height, offsetX2, offsetY2, width2, height2, bufferChoice, filter);
} finally {
if (scissor) GLStateManager.glEnable(GL11.GL_SCISSOR_TEST);
}
}
public static int createFramebuffer() {
@@ -407,14 +536,18 @@ public class RenderSystem {
// TODO: Proper notification of compute support
public static boolean supportsCompute() {
try {
return GLStateManager.capabilities.GL_ARB_compute_shader;
return GLStateManager.capabilities.GL_ARB_compute_shader || GLStateManager.capabilities.OpenGL43;
} catch (Exception ignored) {
return false;
}
}
public static int createBuffers() {
return RENDER_BACKEND.genBuffers();
return GLStateManager.glGenBuffers();
}
public static int getIndexedBufferBinding(int target, int index) {
return RENDER_BACKEND.getIndexedBufferBinding(target, index);
}
public static void bindBufferBase(int target, int index, int buffer) {
@@ -422,17 +555,17 @@ 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) {
RENDER_BACKEND.deleteBuffers(buffer);
GLStateManager.glDeleteBuffers(buffer);
}
public static long getVRAM() {
@@ -469,6 +602,7 @@ public class RenderSystem {
}
public static boolean supportsSnormFormats() {
if (isGLES()) return false;
return GLStateManager.capabilities.OpenGL31;
}
@@ -491,7 +625,7 @@ public class RenderSystem {
public static void destroySampler(int sampler) {
if (!supportsSamplerObjects || sampler == 0) return;
RENDER_BACKEND.deleteSamplers(sampler);
GLStateManager.glDeleteSamplers(sampler);
}
public static void samplerParameteri(int sampler, int pname, int param) {
@@ -516,9 +650,17 @@ public class RenderSystem {
}
}
/** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460". Inlined from StandardMacros.getGlVersion(). */
static String parseGlVersionString(String info) {
final Matcher matcher = SEMVER_PATTERN.matcher(Objects.requireNonNull(info));
/** Parse a GL version string (e.g. "4.6.0 NVIDIA ...") into concatenated digits "460". Strips
* "OpenGL ES " / "OpenGL ES GLSL ES " prefixes so ES contexts parse too. */
public static String parseGlVersionString(String info) {
Objects.requireNonNull(info);
String stripped = info;
if (stripped.startsWith("OpenGL ES GLSL ES ")) {
stripped = stripped.substring("OpenGL ES GLSL ES ".length());
} else if (stripped.startsWith("OpenGL ES ")) {
stripped = stripped.substring("OpenGL ES ".length());
}
final Matcher matcher = SEMVER_PATTERN.matcher(stripped);
if (!matcher.matches()) {
throw new IllegalStateException("Could not parse GL version from \"" + info + "\"");
}
@@ -528,4 +670,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;
}
}
@@ -4,7 +4,6 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Iterator;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
public final class BackendManager {
@@ -39,8 +38,8 @@ public final class BackendManager {
if (backend.isAvailable() && (best == null || backend.getPriority() > best.getPriority())) {
best = backend;
}
} catch (ServiceConfigurationError | LinkageError e) {
LOGGER.debug("Skipping unavailable backend: {}", e.getMessage());
} catch (Throwable t) {
LOGGER.debug("Skipping unavailable backend: {}", t.getMessage());
}
}
@@ -0,0 +1,5 @@
package com.gtnewhorizons.angelica.glsm.backend;
public interface GLDebugMessageListener {
void onMessage(int source, int type, int id, int severity, int length, long message, long userParam);
}
@@ -1,5 +1,6 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.AMDDebugOutput;
import org.lwjgl.opengl.AMDDebugOutputCallback;
@@ -7,8 +8,12 @@ import org.lwjgl.opengl.ARBClearTexture;
import org.lwjgl.opengl.ARBDebugOutput;
import org.lwjgl.opengl.ARBDebugOutputCallback;
import org.lwjgl.opengl.ARBDirectStateAccess;
import org.lwjgl.opengl.ARBTimerQuery;
import org.lwjgl.opengl.ContextCapabilities;
import org.lwjgl.opengl.DisplayMode;
import org.lwjgl.opengl.EXTDirectStateAccess;
import org.lwjgl.opengl.EXTShaderImageLoadStore;
import org.lwjgl.opengl.GLContext;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.lwjgl.opengl.GL13;
@@ -26,6 +31,8 @@ import org.lwjgl.opengl.GL44;
import org.lwjgl.opengl.GL45;
import org.lwjgl.opengl.KHRDebug;
import org.lwjgl.opengl.KHRDebugCallback;
import org.lwjgl.opengl.Display;
import java.nio.ByteBuffer;
import java.nio.DoubleBuffer;
@@ -67,7 +74,7 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
@Override
public void init() {
caps = org.lwjgl.opengl.GLContext.getCapabilities();
caps = GLContext.getCapabilities();
activeDebugExtension = 0;
}
@@ -86,9 +93,43 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
return "OpenGL (LWJGL2)";
}
@Override
public boolean hasContext() {
try {
return GLContext.getCapabilities() != null;
} catch (Exception e) {
return false;
}
}
@Override
public boolean isCurrent() {
try {
return Display.isCurrent();
} catch (Exception e) {
return false;
}
}
@Override
public int getMinGLSLVersion() {return 330;}
@Override
protected void setSwapInterval(boolean vsync) {Display.setVSyncEnabled(vsync);}
@Override
public int getDisplayRefreshRateHz() {
final DisplayMode mode = Display.getDisplayMode();
if (mode != null && mode.getFrequency() > 0) return mode.getFrequency();
final DisplayMode desktop = Display.getDesktopDisplayMode();
return desktop == null ? 0 : desktop.getFrequency();
}
@Override
public boolean isAnisotropicSupported() {
return caps != null && caps.GL_EXT_texture_filter_anisotropic;
}
@Override
public void flush() {GL11.glFlush();}
@@ -300,20 +341,29 @@ 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);
GL43.glMultiDrawElementsIndirect(mode, type, indirect, drawcount, stride);
}
@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
public void drawElementsBaseVertex(int mode, int count, int type, long indices, int baseVertex) {
// On GL path, LWJGL3 service dispatches directly. This is only called via VulkanRenderBackend.
throw new UnsupportedOperationException("GLRenderBackend.drawElementsBaseVertex should not be called — LWJGL service handles this");
GL32.glDrawElementsBaseVertex(mode, count, type, indices, baseVertex);
}
@Override
@@ -497,11 +547,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);
@@ -607,6 +652,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);
@@ -807,6 +857,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);
@@ -817,6 +872,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);
@@ -882,6 +957,32 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL30.glBindBufferBase(target, index, buffer);
}
@Override
public void bindBufferRange(int target, int index, int buffer, long offset, long size) {
GL30.glBindBufferRange(target, index, buffer, offset, size);
}
@Override
public int getUniformBlockIndex(int program, CharSequence name) {
return GL31.glGetUniformBlockIndex(program, name);
}
@Override
public void uniformBlockBinding(int program, int blockIndex, int binding) {
GL31.glUniformBlockBinding(program, blockIndex, binding);
}
@Override
public int getIndexedBufferBinding(int target, int index) {
final int pname = (target == GL43.GL_SHADER_STORAGE_BUFFER) ? GL43.GL_SHADER_STORAGE_BUFFER_BINDING : GL31.GL_UNIFORM_BUFFER_BINDING;
return GL30.glGetInteger(pname, index);
}
@Override
public int getIntegerIndexed(int pname, int index) {
return GL30.glGetInteger(pname, index);
}
@Override
public void bufferData(int target, long size, int usage) {
GL15.glBufferData(target, size, usage);
@@ -1013,11 +1114,26 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
return GL15.glIsBuffer(buffer);
}
@Override
public boolean isTexture(int texture) {
return GL11.glIsTexture(texture);
}
@Override public boolean isFramebuffer(int framebuffer) { return GL30.glIsFramebuffer(framebuffer); }
@Override public boolean isRenderbuffer(int renderbuffer) { return GL30.glIsRenderbuffer(renderbuffer); }
@Override public boolean isSampler(int sampler) { return GL33.glIsSampler(sampler); }
@Override public boolean isQuery(int query) { return GL15.glIsQuery(query); }
@Override
public ByteBuffer mapBufferRange(int target, long offset, long length, int access) {
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();
@@ -1033,6 +1149,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);
@@ -1204,6 +1325,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);
@@ -1224,6 +1350,11 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
return ARBDirectStateAccess.glGetTextureParameteri(texture, pname);
}
@Override
public float getTextureParameterf(int texture, int target, int pname) {
return ARBDirectStateAccess.glGetTextureParameterf(texture, pname);
}
@Override
public int getTextureLevelParameteri(int texture, int level, int pname) {
return ARBDirectStateAccess.glGetTextureLevelParameteri(texture, level, pname);
@@ -1249,6 +1380,16 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
GL11.glGetFloat(pname, params);
}
@Override
public double getDouble(int pname) {
return GL11.glGetDouble(pname);
}
@Override
public void getDouble(int pname, DoubleBuffer params) {
GL11.glGetDouble(pname, params);
}
@Override
public boolean getBoolean(int pname) {
return GL11.glGetBoolean(pname);
@@ -1290,6 +1431,66 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
LWJGL.glDeleteSync(sync);
}
@Override
public boolean supportsGpuProfiling() {
if (!caps.OpenGL33 && !caps.GL_ARB_timer_query) return false;
return GL15.glGetQuery(GL33.GL_TIMESTAMP, GL15.GL_QUERY_COUNTER_BITS) > 0;
}
@Override
public int genQuery() {
return GL15.glGenQueries();
}
@Override
public void deleteQuery(int query) {
GL15.glDeleteQueries(query);
}
@Override
public void queryCounter(int query) {
if (caps.OpenGL33) {
GL33.glQueryCounter(query, GL33.GL_TIMESTAMP);
} else {
ARBTimerQuery.glQueryCounter(query, GL33.GL_TIMESTAMP);
}
}
@Override
public boolean isQueryResultAvailable(int query) {
return GL15.glGetQueryObjecti(query, GL15.GL_QUERY_RESULT_AVAILABLE) != 0;
}
@Override
public long getQueryResult64(int query) {
return caps.OpenGL33 ? GL33.glGetQueryObjectui64(query, GL15.GL_QUERY_RESULT) : ARBTimerQuery.glGetQueryObjectui64(query, GL15.GL_QUERY_RESULT);
}
@Override
public long getGpuTimestamp() {
return GL32.glGetInteger64(GL33.GL_TIMESTAMP);
}
@Override public void genQueries(IntBuffer ids) { GL15.glGenQueries(ids); }
@Override public int genQueries() { return GL15.glGenQueries(); }
@Override public void deleteQueries(int id) { GL15.glDeleteQueries(id); }
@Override public void beginQuery(int target, int id) { GL15.glBeginQuery(target, id); }
@Override public void endQuery(int target) { GL15.glEndQuery(target); }
@Override public void getQueryObjectui(int id, int pname, IntBuffer params) { GL15.glGetQueryObjectu(id, pname, params); }
@Override public int getQueryObjecti(int id, int pname) { return GL15.glGetQueryObjecti(id, pname); }
@Override public void waitSync(long sync, int flags, long timeout) { LWJGL.glWaitSync(sync, flags, timeout); }
@Override public int getSynci(long sync, int pname, IntBuffer length) { return LWJGL.glGetSynci(sync, pname, length); }
@Override public void queryCounter(int id, int target) {
if (caps.OpenGL33) GL33.glQueryCounter(id, target); else ARBTimerQuery.glQueryCounter(id, target);
}
@Override public long getQueryObjectui64(int id, int pname) {
return caps.OpenGL33 ? GL33.glGetQueryObjectui64(id, pname) : ARBTimerQuery.glGetQueryObjectui64(id, pname);
}
@Override public void uniform3fv(int location, float[] values) { GL20.glUniform3f(location, values[0], values[1], values[2]); }
@Override public void uniform4fv(int location, float[] values) { GL20.glUniform4f(location, values[0], values[1], values[2], values[3]); }
@Override public void bindFragDataLocation(int program, int colorNumber, CharSequence name) { GL30.glBindFragDataLocation(program, colorNumber, name); }
@Override
public void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, ByteBuffer data) {
GL43.glClearBufferSubData(target, internalFormat, offset, size, format, type, data);
@@ -1306,7 +1507,13 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
@Override
public void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format) {
GL42.glBindImageTexture(unit, texture, level, layered, layer, access, format);
if (GLStateManager.capabilities.OpenGL42 || GLStateManager.capabilities.GL_ARB_shader_image_load_store) {
GL42.glBindImageTexture(unit, texture, level, layered, layer, access, format);
} else if (GLStateManager.capabilities.GL_EXT_shader_image_load_store) {
EXTShaderImageLoadStore.glBindImageTextureEXT(unit, texture, level, layered, layer, access, format);
} else {
GLStateManager.warnOnce("bindImageTexture", "glBindImageTexture ignored: no image load/store support");
}
}
@Override
@@ -1331,7 +1538,7 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
@Override
public int setupDebugOutput(DebugMessageHandler handler) {
if (caps.OpenGL43 || caps.GL_KHR_debug) {
khrCallback = new KHRDebugCallback((source, type, id, severity, message) -> handler.handleMessage(source, type, id, severity, message));
khrCallback = new KHRDebugCallback(handler::handleMessage);
GL43.glDebugMessageControl(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_HIGH, null, true);
GL43.glDebugMessageControl(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_MEDIUM, null, false);
GL43.glDebugMessageControl(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_LOW, null, false);
@@ -1346,7 +1553,7 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
}
return 1;
} else if (caps.GL_ARB_debug_output) {
arbCallback = new ARBDebugOutputCallback((source, type, id, severity, message) -> handler.handleMessage(source, type, id, severity, message));
arbCallback = new ARBDebugOutputCallback(handler::handleMessage);
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_HIGH, null, true);
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_MEDIUM, null, false);
ARBDebugOutput.glDebugMessageControlARB(GL11.GL_DONT_CARE, GL11.GL_DONT_CARE, GL43.GL_DEBUG_SEVERITY_LOW, null, false);
@@ -1460,4 +1667,76 @@ public final class Lwjgl2GLRenderBackend extends RenderBackend {
}
}
@Override
public void debugMessageControl(int source, int type, int severity, IntBuffer ids, boolean enabled) {
if (activeDebugExtension == 1) {
GL43.glDebugMessageControl(source, type, severity, ids, enabled);
}
}
@Override
public void multiDrawArrays(int mode, IntBuffer firsts, IntBuffer counts) {
GL14.glMultiDrawArrays(mode, firsts, counts);
}
@Override
public void primitiveRestartIndex(int index) {
GL31.glPrimitiveRestartIndex(index);
}
@Override
public void pointParameterf(int pname, float param) {
GL14.glPointParameterf(pname, param);
}
@Override
public void pointParameteri(int pname, int param) {
GL14.glPointParameteri(pname, param);
}
@Override
public void getActiveAttrib(int program, int index, IntBuffer length, IntBuffer size, IntBuffer type, ByteBuffer name) {
GL20.glGetActiveAttrib(program, index, length, size, type, name);
}
@Override
public String getActiveAttrib(int program, int index, int maxLength, IntBuffer sizeType) {
return GL20.glGetActiveAttrib(program, index, maxLength, sizeType);
}
@Override
public int genRenderbuffers() {
return GL30.glGenRenderbuffers();
}
@Override
public void deleteRenderbuffers(int renderbuffer) {
GL30.glDeleteRenderbuffers(renderbuffer);
}
@Override
public void bindRenderbuffer(int target, int renderbuffer) {
GL30.glBindRenderbuffer(target, renderbuffer);
}
@Override
public void renderbufferStorage(int target, int internalformat, int width, int height) {
GL30.glRenderbufferStorage(target, internalformat, width, height);
}
@Override
public void renderbufferStorageMultisample(int target, int samples, int internalformat, int width, int height) {
GL30.glRenderbufferStorageMultisample(target, samples, internalformat, width, height);
}
@Override
public void framebufferRenderbuffer(int target, int attachment, int renderbuffertarget, int renderbuffer) {
GL30.glFramebufferRenderbuffer(target, attachment, renderbuffertarget, renderbuffer);
}
@Override
public void clearBufferData(int target, int internalformat, int format, int type, ByteBuffer data) {
GL43.glClearBufferData(target, internalformat, format, type, data);
}
}
@@ -1,6 +1,8 @@
package com.gtnewhorizons.angelica.glsm.backend;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import org.lwjgl.opengl.GL20;
import java.nio.ByteBuffer;
@@ -8,28 +10,123 @@ 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.
*/
public abstract class RenderBackend {
public void onPostWindowCreate(long window) {}
public abstract void init();
public abstract void shutdown();
public abstract boolean isAvailable();
public abstract String getName();
/** Returns true if the current thread has a valid render context. */
public abstract boolean hasContext();
/** Returns true if the calling thread owns the "GL Context" */
public abstract boolean isCurrent();
/** 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 boolean isIndirectRequired() { return false; }
public boolean supportsGeometryShaders() { return true; }
public boolean isSDLGPU() { return false; }
public boolean framebufferCompletenessIsMeaningful() { return true; }
public void onPersistentBufferWrite(int glId, long offset, long size) {}
public boolean isAnisotropicSupported() { return true; }
public boolean supportsGpuDrivenCulling() { return RenderSystem.supportsCompute(); }
public void beginComputeDispatchBatch() {}
public void endComputeDispatchBatch() {}
public abstract int getMinGLSLVersion();
public abstract void flush();
public abstract void finish();
public void onFrameBegin() {}
protected abstract void setSwapInterval(boolean vsync);
private VSyncMode preferredVSyncMode = VSyncMode.AUTO;
private VSyncMode effectiveVSyncMode = VSyncMode.ON;
public final void setVSyncMode(VSyncMode preferred, boolean vsyncEnabled) {
preferredVSyncMode = preferred == null ? VSyncMode.AUTO : preferred;
effectiveVSyncMode = applyVSyncMode(preferredVSyncMode, vsyncEnabled);
}
protected VSyncMode applyVSyncMode(VSyncMode preferred, boolean vsyncEnabled) {
final VSyncMode mode = preferred.resolve(vsyncEnabled).swapIntervalEquivalent();
setSwapInterval(mode.usesVSyncSwapInterval());
return mode;
}
public boolean supportsVSyncMode(VSyncMode mode) { return mode != VSyncMode.MAILBOX; }
public int getMinRenderAhead() { return 0; }
public int getMaxRenderAhead() { return 9; }
public void setRenderAheadLimit(int frames) {}
public static int clampRenderAhead(int value, int min, int max) {
if (value <= 0) return min <= 0 ? 0 : max;
return Math.min(Math.max(value, Math.max(min, 1)), max);
}
public final VSyncMode getPreferredVSyncMode() { return preferredVSyncMode; }
public final VSyncMode getEffectiveVSyncMode() { return effectiveVSyncMode; }
public int getDisplayRefreshRateHz() { return 0; }
public static int refreshHzFrom(int numerator, int denominator, float fallbackHz) {
if (numerator > 0 && denominator > 0) return (numerator + denominator - 1) / denominator;
return fallbackHz > 0.0f ? (int) Math.ceil(fallbackHz) : 0;
}
public String getPresentDebugInfo() { return null; }
public void onFrameBegin() {
if (GLStateManager.takeStateSeedPending()) {
GLStateManager.replayStateToBackend();
}
}
public void onFrameEnd() {}
/** Fired by lwjgl3ify Display before a mutation that recreates the swapchain. */
public void onPreSwapchainInvalidatingChange(Object change) {}
public boolean handleMakeCurrent(Object drawable) { return false; }
public boolean handleReleaseContext(Object drawable) { return false; }
public boolean handleSwapBuffers() { return false; }
public void onRenderThreadReleased(Thread thread) {}
public String getTransferDebugInfo() { return null; }
public boolean bindVoxelizationRegion(int ssboBinding, int vertexBufferGlId, long openPass, float x, float y, float z) { return false; }
public long beginVoxelizationBatch(int ssboBinding) { return 0L; }
public void voxelizeRange(long pass, int vertexOffset, int vertexCount) {}
public void endVoxelizationBatch(long pass) {}
public abstract void enable(int cap);
public abstract void enablei(int cap, int index);
public abstract void disable(int cap);
@@ -47,6 +144,7 @@ public abstract class RenderBackend {
public abstract void frontFace(int mode);
public abstract void polygonMode(int face, int mode);
public abstract void polygonOffset(float factor, float units);
public void polygonOffsetClamp(float factor, float units, float clamp) { throw new UnsupportedOperationException(getName() + ": polygon offset clamp unsupported"); }
public abstract void stencilFunc(int func, int ref, int mask);
public abstract void stencilOp(int sfail, int dpfail, int dppass);
public abstract void stencilMask(int mask);
@@ -72,10 +170,24 @@ public abstract class RenderBackend {
public void drawElements(int mode, ShortBuffer indices) {}
public void drawElements(int mode, int count, int type, ByteBuffer indices) {}
public abstract void multiDrawElementsIndirect(int mode, int type, long indirect, int drawcount, int stride);
public void multiDrawArraysIndirectCount(int mode, long indirect, long drawcount, int maxdrawcount, int stride) { throw new UnsupportedOperationException(getName() + ": indirect draw-count buffers unsupported"); }
public void multiDrawElementsIndirectCount(int mode, int type, long indirect, long drawcount, int maxdrawcount, int stride) { throw new UnsupportedOperationException(getName() + ": indirect draw-count buffers unsupported"); }
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 void drawRangeElements(int mode, int start, int end, int count, int type, long indices) {
drawElements(mode, count, type, indices);
}
public void drawRangeElementsBaseVertex(int mode, int start, int end, int count, int type, long indices, int baseVertex) {
drawElementsBaseVertex(mode, count, type, indices, baseVertex);
}
public abstract void multiDrawArrays(int mode, IntBuffer firsts, IntBuffer counts);
public abstract void primitiveRestartIndex(int index);
public abstract void pointParameterf(int pname, float param);
public abstract void pointParameteri(int pname, int param);
public abstract void drawBuffer(int mode);
public abstract void dispatchCompute(int numGroupsX, int numGroupsY, int numGroupsZ);
public abstract void dispatchComputeIndirect(long offset);
@@ -112,7 +224,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) {}
@@ -127,6 +238,12 @@ public abstract class RenderBackend {
public abstract void bindFramebuffer(int target, int framebuffer);
public abstract void framebufferTexture2D(int target, int attachment, int textarget, int texture, int level);
public abstract void framebufferTexture(int target, int attachment, int texture, int level);
public abstract int genRenderbuffers();
public abstract void deleteRenderbuffers(int renderbuffer);
public abstract void bindRenderbuffer(int target, int renderbuffer);
public abstract void renderbufferStorage(int target, int internalformat, int width, int height);
public abstract void renderbufferStorageMultisample(int target, int samples, int internalformat, int width, int height);
public abstract void framebufferRenderbuffer(int target, int attachment, int renderbuffertarget, int renderbuffer);
public abstract int checkFramebufferStatus(int target);
public abstract void blitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter);
public abstract void drawBuffers(int buffer);
@@ -137,12 +254,14 @@ 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);
public abstract void deleteShader(int shader);
public abstract void shaderSource(int shader, CharSequence source);
public abstract void compileShader(int shader);
public void specializeShader(int shader, CharSequence entryPoint, IntBuffer constantIndex, IntBuffer constantValue) { throw new UnsupportedOperationException(getName() + ": externally supplied SPIR-V binaries unsupported"); }
public abstract int createProgram();
public abstract void deleteProgram(int program);
public abstract void attachShader(int program, int shader);
@@ -164,7 +283,10 @@ public abstract class RenderBackend {
public abstract void getProgramiv(int program, int pname, IntBuffer params);
public abstract String getActiveUniform(int program, int index, int maxLength, IntBuffer sizeType);
public abstract void getActiveUniform(int program, int index, IntBuffer length, IntBuffer size, IntBuffer type, ByteBuffer name);
public abstract void getActiveAttrib(int program, int index, IntBuffer length, IntBuffer size, IntBuffer type, ByteBuffer name);
public abstract String getActiveAttrib(int program, int index, int maxLength, IntBuffer sizeType);
public abstract void bindAttribLocation(int program, int index, CharSequence name);
public void bindFragDataLocation(int program, int colorNumber, CharSequence name) {}
public abstract int getAttribLocation(int program, CharSequence name);
public abstract int getAttribLocation(int program, ByteBuffer name);
public abstract int getUniformLocation(int program, CharSequence name);
@@ -181,14 +303,21 @@ public abstract class RenderBackend {
public abstract void uniform1i(int location, int v0);
public abstract void uniform1f(int location, float v0);
public void uniform1fv(int location, FloatBuffer values) {}
public void uniform3fv(int location, float[] values) {}
public void uniform4fv(int location, float[] values) {}
public abstract void uniform2f(int location, float v0, float v1);
public abstract void uniform2i(int location, int v0, int v1);
public abstract void uniform3f(int location, float v0, float v1, float v2);
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) {}
@@ -204,6 +333,11 @@ public abstract class RenderBackend {
public abstract void deleteBuffers(IntBuffer buffers);
public abstract void bindBuffer(int target, int buffer);
public abstract void bindBufferBase(int target, int index, int buffer);
public abstract void bindBufferRange(int target, int index, int buffer, long offset, long size);
public abstract int getUniformBlockIndex(int program, CharSequence name);
public abstract void uniformBlockBinding(int program, int blockIndex, int binding);
public abstract int getIndexedBufferBinding(int target, int index);
public abstract int getIntegerIndexed(int pname, int index);
public abstract void bufferData(int target, long size, int usage);
public abstract void bufferData(int target, ByteBuffer data, int usage);
public abstract void bufferData(int target, FloatBuffer data, int usage);
@@ -230,10 +364,22 @@ 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 long mapBufferRangeAddress(int target, long offset, long length, int access) {
final ByteBuffer buf = mapBufferRange(target, offset, length, access);
return buf == null ? 0L : MemoryUtilities.memAddress0(buf);
}
public abstract boolean isTexture(int texture);
public abstract boolean isFramebuffer(int framebuffer);
public abstract boolean isRenderbuffer(int renderbuffer);
public abstract boolean isSampler(int sampler);
public abstract boolean isQuery(int query);
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));
@@ -272,17 +418,22 @@ 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);
public abstract void copyTextureSubImage2D(int texture, int target, int level, int xoffset, int yoffset, int x, int y, int width, int height);
public abstract int getTextureParameteri(int texture, int target, int pname);
public abstract float getTextureParameterf(int texture, int target, int pname);
public abstract int getTextureLevelParameteri(int texture, int level, int pname);
public abstract int getInteger(int pname);
public abstract void getInteger(int pname, IntBuffer params);
public void getInteger(int pname, int[] params) { if (params.length > 0) params[0] = getInteger(pname); }
public abstract float getFloat(int pname);
public abstract void getFloat(int pname, FloatBuffer params);
public double getDouble(int pname) { return getFloat(pname); }
public void getDouble(int pname, DoubleBuffer params) { if (params.remaining() > 0) params.put(params.position(), getDouble(pname)); }
public abstract boolean getBoolean(int pname);
public abstract void getBoolean(int pname, ByteBuffer params);
public abstract String getString(int pname);
@@ -292,8 +443,30 @@ public abstract class RenderBackend {
public abstract long fenceSync(int condition, int flags);
public abstract int clientWaitSync(long sync, int flags, long timeout);
public abstract void deleteSync(long sync);
public void waitSync(long sync, int flags, long timeout) { throw new UnsupportedOperationException("sync objects unsupported"); }
public int getSynci(long sync, int pname, IntBuffer length) { throw new UnsupportedOperationException("sync objects unsupported"); }
public void genQueries(IntBuffer ids) { throw new UnsupportedOperationException("occlusion queries unsupported"); }
public int genQueries() { throw new UnsupportedOperationException("occlusion queries unsupported"); }
public void deleteQueries(int id) { throw new UnsupportedOperationException("occlusion queries unsupported"); }
public void beginQuery(int target, int id) { throw new UnsupportedOperationException("occlusion queries unsupported"); }
public void endQuery(int target) { throw new UnsupportedOperationException("occlusion queries unsupported"); }
public void getQueryObjectui(int id, int pname, IntBuffer params) { throw new UnsupportedOperationException("occlusion queries unsupported"); }
public int getQueryObjecti(int id, int pname) { throw new UnsupportedOperationException("occlusion queries unsupported"); }
public void queryCounter(int id, int target) { throw new UnsupportedOperationException("timer queries unsupported"); }
public long getQueryObjectui64(int id, int pname) { throw new UnsupportedOperationException("timer queries unsupported"); }
public boolean supportsGpuProfiling() { return false; }
public int genQuery() { throw new UnsupportedOperationException("timer queries unsupported"); }
public void deleteQuery(int query) { throw new UnsupportedOperationException("timer queries unsupported"); }
public void queryCounter(int query) { throw new UnsupportedOperationException("timer queries unsupported"); }
public boolean isQueryResultAvailable(int query) { throw new UnsupportedOperationException("timer queries unsupported"); }
public long getQueryResult64(int query) { throw new UnsupportedOperationException("timer queries unsupported"); }
public long getGpuTimestamp() { throw new UnsupportedOperationException("timer queries unsupported"); }
public abstract void clearBufferSubData(int target, int internalFormat, long offset, long size, int format, int type, ByteBuffer data);
public abstract void clearBufferData(int target, int internalformat, int format, int type, ByteBuffer data);
public abstract void clearTexImage(int texture, int level, int format, int type);
public abstract void bindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format);
@@ -319,4 +492,10 @@ public abstract class RenderBackend {
// Debug message insertion
public void debugMessageInsert(int source, int type, int id, int severity, CharSequence message) {}
public void debugMessageControl(int source, int type, int severity, IntBuffer ids, boolean enabled) {}
public void debugMessageCallback(GLDebugMessageListener listener, long userParam) {}
public int getDebugMessageLog(int count, IntBuffer sources, IntBuffer types, IntBuffer ids, IntBuffer severities, IntBuffer lengths, ByteBuffer messageLog) { return 0; }
}
@@ -0,0 +1,54 @@
package com.gtnewhorizons.angelica.glsm.backend;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public enum VSyncMode {
AUTO,
ON,
MAILBOX,
OFF;
private static final List<VSyncMode> OFF_ORDER = List.of(OFF, MAILBOX, ON);
private static final List<VSyncMode> MAILBOX_ORDER = List.of(MAILBOX, ON);
private static final List<VSyncMode> ON_ORDER = List.of(ON);
private static final List<VSyncMode> DISPLAY_ORDER = List.of(OFF, ON, MAILBOX);
public boolean blocksOnVBlank() {
return this == ON;
}
public boolean usesVSyncSwapInterval() {
return this == ON || this == MAILBOX;
}
public VSyncMode swapIntervalEquivalent() {
return this == MAILBOX ? ON : this;
}
public VSyncMode resolve(boolean vsyncEnabled) {
return this != AUTO ? this : (vsyncEnabled ? ON : OFF);
}
public List<VSyncMode> fallbackOrder() {
return switch (this) {
case OFF -> OFF_ORDER;
case MAILBOX -> MAILBOX_ORDER;
default -> ON_ORDER;
};
}
public boolean tearFree() {
return this != OFF;
}
public static List<VSyncMode> selectable(Predicate<VSyncMode> supported) {
final List<VSyncMode> modes = new ArrayList<>(DISPLAY_ORDER.size());
for (VSyncMode mode : DISPLAY_ORDER) {
if (supported.test(mode)) modes.add(mode);
}
return modes;
}
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.dsa;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import org.lwjgl.opengl.GL11;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
@@ -75,6 +76,11 @@ public class DSAARB extends DSAUnsupported {
return GLStateManager.getTexParameterOrDefault(texture, pname, () -> RENDER_BACKEND.getTextureParameteri(texture, target, pname));
}
@Override
public float getTexParameterf(int texture, int target, int pname) {
return RENDER_BACKEND.getTextureParameterf(texture, target, pname);
}
@Override
public int getTexLevelParameteri(int texture, int level, int pname) {
return RENDER_BACKEND.getTextureLevelParameteri(texture, level, pname);
@@ -87,25 +93,26 @@ public class DSAARB extends DSAUnsupported {
@Override
public void bindTextureToUnit(int unit, int texture) {
if(GLStateManager.getBoundTextureForServerState(unit) == texture) return;
if (texture == 0) {
super.bindTextureToUnit(unit, texture);
} else {
RENDER_BACKEND.bindTextureUnit(unit, texture);
GLStateManager.getTextures().getTextureUnitBindings(unit).setBinding(texture);
}
bindTextureToUnit(0, unit, texture);
}
@Override
public void bindTextureToUnit(int target, int unit, int texture) {
bindTextureToUnit(unit, texture);
if(GLStateManager.getBoundTextureForServerState(unit) == texture) return;
if (texture == 0) {
super.bindTextureToUnit(target != 0 ? target : GL11.GL_TEXTURE_2D, unit, texture);
} else {
RENDER_BACKEND.bindTextureUnit(unit, texture);
GLStateManager.getTextures().getTextureUnitBindings(unit).setBinding(texture, target);
GLStateManager.trackMaxBoundTextureUnit(unit);
}
}
@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;
}
@@ -23,6 +23,8 @@ public interface DSAAccess {
int getTexParameteri(int texture, int target, int pname);
float getTexParameterf(int texture, int target, int pname);
int getTexLevelParameteri(int texture, int level, int pname);
void copyTexSubImage2D(int destTexture, int target, int i, int i1, int i2, int i3, int i4, int width, int height);
@@ -90,6 +90,12 @@ public class DSAUnsupported implements DSAAccess {
return GLStateManager.glGetTexParameteri(target, pname);
}
@Override
public float getTexParameterf(int texture, int target, int pname) {
GLStateManager.glBindTexture(GL11.GL_TEXTURE_2D, texture);
return GLStateManager.glGetTexParameterf(target, pname);
}
@Override
public int getTexLevelParameteri(int texture, int level, int pname) {
final int previous = GLStateManager.getBoundTextureForServerState();
@@ -121,9 +127,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;
@@ -0,0 +1,119 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
public final class FFPUniformBlock {
public static final String BLOCK_NAME = "FFPUniformBlock";
public static final int BINDING_POINT = 0;
private static final StringBuilder DECL = new StringBuilder(2048);
private static int cursor = 0;
static final Object2IntMap<String> MEMBER_OFFSETS = new Object2IntLinkedOpenHashMap<>();
private static int align(int alignment) {
cursor = (cursor + alignment - 1) & -alignment;
return cursor;
}
private static int member(String type, String name, int alignment, int size) {
DECL.append(" ").append(type).append(' ').append(name).append(";\n");
final int offset = align(alignment);
cursor += size;
MEMBER_OFFSETS.put(name, offset);
return offset;
}
private static int mat4(String name) { return member("mat4", name, 16, 64); }
private static int mat3(String name) { return member("mat3", name, 16, 48); }
private static int vec4(String name) { return member("vec4", name, 16, 16); }
private static int vec3(String name) { return member("vec3", name, 16, 12); }
private static int vec2(String name) { return member("vec2", name, 8, 8); }
private static int flt(String name) { return member("float", name, 4, 4); }
private static int integer(String name) { return member("int", name, 4, 4); }
private static int vec4Array(String name, int count) {
DECL.append(" vec4 ").append(name).append('[').append(count).append("];\n");
final int offset = align(16);
cursor += 16 * count;
MEMBER_OFFSETS.put(name + "[0]", offset);
return offset;
}
static { DECL.append("layout(std140) uniform ").append(BLOCK_NAME).append(" {\n"); }
public static final int MODEL_VIEW_MATRIX = mat4("u_ModelViewMatrix");
public static final int PROJECTION_MATRIX = mat4("u_ProjectionMatrix");
public static final int MVP_MATRIX = mat4("u_MVPMatrix");
public static final int NORMAL_MATRIX = mat3("u_NormalMatrix");
public static final int TEXTURE_MATRIX_0 = mat4("u_TextureMatrix0");
public static final int TEXTURE_MATRIX_2 = mat4("u_TextureMatrix2");
public static final int TEXTURE_MATRIX_3 = mat4("u_TextureMatrix3");
public static final int LIGHTMAP_TEXTURE_MATRIX = mat4("u_LightmapTextureMatrix");
public static final int CURRENT_COLOR = vec4("u_CurrentColor");
public static final int CURRENT_TEX_COORD_0 = vec4("u_CurrentTexCoord0");
public static final int CURRENT_TEX_COORD_2 = vec4("u_CurrentTexCoord2");
public static final int CURRENT_TEX_COORD_3 = vec4("u_CurrentTexCoord3");
public static final int VIEWPORT = vec4("u_Viewport");
public static final int CURRENT_NORMAL = vec3("u_CurrentNormal");
public static final int CURRENT_LIGHTMAP_COORD = vec2("u_CurrentLightmapCoord");
public static final int VIEWPORT_SIZE = vec2("u_ViewportSize");
public static final int NORMAL_SCALE = flt("u_NormalScale");
public static final int MATERIAL_SHININESS = flt("u_MaterialShininess");
public static final int ALPHA_REF = flt("u_AlphaRef");
public static final int LINE_WIDTH = flt("u_LineWidth");
public static final int LINE_STIPPLE = integer("u_LineStipple");
public static final int TEX_GEN_OBJ_PLANE_S = vec4("u_TexGenObjPlaneS");
public static final int TEX_GEN_OBJ_PLANE_T = vec4("u_TexGenObjPlaneT");
public static final int TEX_GEN_OBJ_PLANE_R = vec4("u_TexGenObjPlaneR");
public static final int TEX_GEN_OBJ_PLANE_Q = vec4("u_TexGenObjPlaneQ");
public static final int TEX_GEN_EYE_PLANE_S = vec4("u_TexGenEyePlaneS");
public static final int TEX_GEN_EYE_PLANE_T = vec4("u_TexGenEyePlaneT");
public static final int TEX_GEN_EYE_PLANE_R = vec4("u_TexGenEyePlaneR");
public static final int TEX_GEN_EYE_PLANE_Q = vec4("u_TexGenEyePlaneQ");
public static final int CLIP_PLANES = vec4Array("u_ClipPlane", 8);
public static final int LIGHT_MODEL_AMBIENT = vec4("u_LightModelAmbient");
public static final int MATERIAL_EMISSION = vec4("u_MaterialEmission");
public static final int MATERIAL_AMBIENT = vec4("u_MaterialAmbient");
public static final int MATERIAL_DIFFUSE = vec4("u_MaterialDiffuse");
public static final int MATERIAL_SPECULAR = vec4("u_MaterialSpecular");
public static final int LIGHT0_AMBIENT = vec4("u_Light0Ambient");
public static final int LIGHT0_DIFFUSE = vec4("u_Light0Diffuse");
public static final int LIGHT0_SPECULAR = vec4("u_Light0Specular");
public static final int LIGHT0_POSITION = vec4("u_Light0Position");
public static final int LIGHT1_AMBIENT = vec4("u_Light1Ambient");
public static final int LIGHT1_DIFFUSE = vec4("u_Light1Diffuse");
public static final int LIGHT1_SPECULAR = vec4("u_Light1Specular");
public static final int LIGHT1_POSITION = vec4("u_Light1Position");
public static final int SCENE_COLOR = vec4("u_SceneColor");
public static final int LIGHT_PROD0_AMBIENT = vec3("u_LightProd0Ambient");
public static final int LIGHT_PROD0_DIFFUSE = vec3("u_LightProd0Diffuse");
public static final int LIGHT_PROD0_SPECULAR = vec3("u_LightProd0Specular");
public static final int LIGHT_PROD1_AMBIENT = vec3("u_LightProd1Ambient");
public static final int LIGHT_PROD1_DIFFUSE = vec3("u_LightProd1Diffuse");
public static final int LIGHT_PROD1_SPECULAR = vec3("u_LightProd1Specular");
public static final int TEX_ENV_COLOR_0 = vec4("u_TexEnvColor0");
public static final int TEX_ENV_COLOR_1 = vec4("u_TexEnvColor1");
public static final int TEX_ENV_COLOR_2 = vec4("u_TexEnvColor2");
public static final int TEX_ENV_COLOR_3 = vec4("u_TexEnvColor3");
public static final int OVERLAY_COLOR = vec4("u_OverlayColor");
public static final int FOG_PARAMS = vec4("u_FogParams");
public static final int FOG_COLOR = vec4("u_FogColor");
public static final int SIZE;
public static final String GLSL_DECL;
static {
SIZE = (cursor + 15) & -16;
DECL.append("};\n");
GLSL_DECL = DECL.toString();
}
private FFPUniformBlock() {}
}
@@ -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 (12) + unit 0 (47 bits at offset 12)
* long[1..3]: unit 1..3 (47 bits each, low-aligned)
* Only max(1, nrEnabledUnits) longs are significant.
*
@@ -63,12 +63,14 @@ 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 = 12;
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 BIT_LINE_STIPPLE = 11; // 1 bit
private static final int U_ENABLED = 0;
private static final int U_MODE = 1; // 3 bits
@@ -119,6 +121,15 @@ 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);
}
if (GLStateManager.lineStippleActive) {
global |= (1L << BIT_LINE_STIPPLE);
}
// Separate specular
if (GLStateManager.getLightingState().isEnabled()
&& GLStateManager.getLightModel().colorControl == GL12.GL_SEPARATE_SPECULAR_COLOR) {
@@ -129,7 +140,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);
@@ -226,7 +238,9 @@ public final class FragmentKey {
public boolean alphaTestEnabled() { return ((packed[0] >> BIT_ALPHA_TEST) & 1) != 0; }
public int alphaTestFunc() { return (int) ((packed[0] >> BIT_ALPHA_FUNC) & 0x7); }
public boolean separateSpecular() { return ((packed[0] >> BIT_SEPARATE_SPECULAR) & 1) != 0; }
public boolean lineStipple() { return ((packed[0] >> BIT_LINE_STIPPLE) & 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 +266,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 +374,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));
@@ -20,6 +20,7 @@ public final class FragmentShaderGenerator {
sb.append("out vec4 fragColor;\n\n");
sb.append("void main() {\n");
emitLineStipple(sb, key);
emitTextureSampling(sb, key);
emitTexEnvChain(sb, key);
emitSpecularAdd(sb, key);
@@ -37,49 +38,42 @@ 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");
}
if (key.lineStipple()) {
sb.append("flat in vec2 v_LineStart;\n");
}
sb.append('\n');
}
private static void emitLineStipple(StringBuilder sb, FragmentKey key) {
if (!key.lineStipple()) return;
sb.append(" vec2 stippleDelta = abs(gl_FragCoord.xy - v_LineStart);\n");
sb.append(" int stippleBit = int(floor(max(stippleDelta.x, stippleDelta.y) / float(u_LineStipple >> 16))) & 15;\n");
sb.append(" if (((u_LineStipple >> stippleBit) & 1) == 0) discard;\n\n");
}
private static void emitUniforms(StringBuilder sb, FragmentKey key) {
// Samplers for each enabled unit
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (key.unitEnabled(i)) {
sb.append("uniform sampler2D u_Sampler").append(i).append(";\n");
}
}
if (key.alphaTestEnabled()) {
sb.append("uniform float u_AlphaRef;\n");
}
// Per-unit texEnvColor uniforms
for (int i = 0; i < key.nrEnabledUnits(); i++) {
if (key.unitEnabled(i) && key.unitNeedsEnvColor(i)) {
sb.append("uniform vec4 u_TexEnvColor").append(i).append(";\n");
}
}
if (key.fogMode() != FragmentKey.FOG_NONE) {
sb.append("uniform vec4 u_FogParams;\n");
sb.append("uniform vec4 u_FogColor;\n");
}
sb.append(FFPUniformBlock.GLSL_DECL);
sb.append('\n');
}
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 +81,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 +108,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) {
@@ -273,21 +284,27 @@ public final class FragmentShaderGenerator {
sb.append(" // Alpha test\n");
final int func = FragmentKey.decodeAlphaFunc(key.alphaTestFunc());
sb.append(" float qa = round(color.a * 255.0);\n");
switch (func) {
case GL11.GL_NEVER ->
sb.append(" discard;\n");
case GL11.GL_LESS ->
sb.append(" if (color.a >= u_AlphaRef) discard;\n");
case GL11.GL_EQUAL ->
sb.append(" if (color.a != u_AlphaRef) discard;\n");
sb.append(" if (qa >= u_AlphaRef * 255.0) discard;\n");
case GL11.GL_EQUAL -> {
sb.append(" float qr = round(u_AlphaRef * 255.0);\n");
sb.append(" if (qa != qr) discard;\n");
}
case GL11.GL_LEQUAL ->
sb.append(" if (color.a > u_AlphaRef) discard;\n");
sb.append(" if (qa > u_AlphaRef * 255.0) discard;\n");
case GL11.GL_GREATER ->
sb.append(" if (color.a <= u_AlphaRef) discard;\n");
case GL11.GL_NOTEQUAL ->
sb.append(" if (color.a == u_AlphaRef) discard;\n");
sb.append(" if (qa <= u_AlphaRef * 255.0) discard;\n");
case GL11.GL_NOTEQUAL -> {
sb.append(" float qr = round(u_AlphaRef * 255.0);\n");
sb.append(" if (qa == qr) discard;\n");
}
case GL11.GL_GEQUAL ->
sb.append(" if (color.a < u_AlphaRef) discard;\n");
sb.append(" if (qa < u_AlphaRef * 255.0) discard;\n");
case GL11.GL_ALWAYS -> {}
}
}
@@ -14,38 +14,40 @@ public final class GeometryShaderGenerator {
sb.append("layout(lines) in;\n");
sb.append("layout(triangle_strip, max_vertices = 4) out;\n\n");
sb.append("uniform vec2 u_ViewportSize;\n");
sb.append("uniform float u_LineWidth;\n\n");
sb.append(FFPUniformBlock.GLSL_DECL);
sb.append('\n');
sb.append("// Pass-through varyings\n");
sb.append("in vec4 v_Color_gs[];\n");
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");
}
if (key.lineStipple()) {
sb.append("flat in vec2 v_LineStart_gs[];\n");
}
sb.append('\n');
sb.append("out vec4 v_Color;\n");
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");
}
if (key.lineStipple()) {
sb.append("flat out vec2 v_LineStart;\n");
}
sb.append('\n');
sb.append("void main() {\n");
@@ -82,15 +84,16 @@ 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");
}
if (key.lineStipple()) {
sb.append(" v_LineStart = v_LineStart_gs[0];\n");
}
if (key.clipPlanesEnabled()) {
for (int i = 0; i < 8; i++) {
@@ -0,0 +1,18 @@
package com.gtnewhorizons.angelica.glsm.ffp;
public final class InstancedAttribs {
public static final int LOC_MATRIX_COL0 = 7;
public static final int LOC_MATRIX_COL1 = 8;
public static final int LOC_MATRIX_COL2 = 9;
public static final int LOC_MATRIX_COL3 = 10;
public static final int LOC_COLOR = 11;
public static final int LOC_LIGHTMAP = 12;
public static final int OFFSET_MATRIX = 0;
public static final int OFFSET_COLOR = 64;
public static final int OFFSET_LIGHTMAP = 68;
public static final int STRIDE = 80;
private InstancedAttribs() {}
}
@@ -1,16 +1,23 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CompatShaderTransformer;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.RenderSystem;
import com.gtnewhorizons.angelica.glsm.backend.RenderBackend;
import lombok.Getter;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.KHRDebug;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicInteger;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
@@ -26,159 +33,19 @@ public class Program {
@Getter private final VertexKey vertexKey;
@Getter private final FragmentKey fragmentKey;
// 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;
// Current state defaults
public int locCurrentNormal = -1;
public int locCurrentColor = -1;
public int locCurrentTexCoord = -1;
public int locCurrentLightmapCoord = -1;
public int locLightmapTextureMatrix = -1;
// Lighting - color material path (raw values)
public int locLightModelAmbient = -1;
public int locMaterialEmission = -1;
public int locMaterialAmbient = -1;
public int locMaterialDiffuse = -1;
public int locMaterialSpecular = -1;
public int locMaterialShininess = -1;
public int locLight0Ambient = -1;
public int locLight0Diffuse = -1;
public int locLight0Specular = -1;
public int locLight1Ambient = -1;
public int locLight1Diffuse = -1;
public int locLight1Specular = -1;
// Lighting - pre-computed path
public int locSceneColor = -1;
public int locLightProd0Ambient = -1;
public int locLightProd0Diffuse = -1;
public int locLightProd0Specular = -1;
public int locLightProd1Ambient = -1;
public int locLightProd1Diffuse = -1;
public int locLightProd1Specular = -1;
// Light positions
public int locLight0Position = -1;
public int locLight1Position = -1;
// Normal scale (for rescale normals)
public int locNormalScale = -1;
// TexGen plane uniforms
public int locTexGenObjPlaneS = -1;
public int locTexGenObjPlaneT = -1;
public int locTexGenObjPlaneR = -1;
public int locTexGenObjPlaneQ = -1;
public int locTexGenEyePlaneS = -1;
public int locTexGenEyePlaneT = -1;
public int locTexGenEyePlaneR = -1;
public int locTexGenEyePlaneQ = -1;
// Clip planes
public int locClipPlanes = -1;
// Wide line emulation (geometry shader)
public int locViewportSize = -1;
public int locLineWidth = -1;
// Fragment uniforms
public final int[] locSampler = { -1, -1, -1, -1 };
public int locAlphaRef = -1;
public final int[] locTexEnvColor = { -1, -1, -1, -1 };
public int locFogParams = -1;
public int locFogColor = -1;
Program(int programId, VertexKey vertexKey, FragmentKey fragmentKey) {
this.programId = programId;
this.vertexKey = vertexKey;
this.fragmentKey = fragmentKey;
resolveLocations();
}
private void resolveLocations() {
locModelViewMatrix = loc("u_ModelViewMatrix");
locProjectionMatrix = loc("u_ProjectionMatrix");
locMVPMatrix = loc("u_MVPMatrix");
locNormalMatrix = loc("u_NormalMatrix");
locTextureMatrix0 = loc("u_TextureMatrix0");
locCurrentNormal = loc("u_CurrentNormal");
locCurrentColor = loc("u_CurrentColor");
locCurrentTexCoord = loc("u_CurrentTexCoord");
locCurrentLightmapCoord = loc("u_CurrentLightmapCoord");
locLightmapTextureMatrix = loc("u_LightmapTextureMatrix");
// Color material path
locLightModelAmbient = loc("u_LightModelAmbient");
locMaterialEmission = loc("u_MaterialEmission");
locMaterialAmbient = loc("u_MaterialAmbient");
locMaterialDiffuse = loc("u_MaterialDiffuse");
locMaterialSpecular = loc("u_MaterialSpecular");
locMaterialShininess = loc("u_MaterialShininess");
locLight0Ambient = loc("u_Light0Ambient");
locLight0Diffuse = loc("u_Light0Diffuse");
locLight0Specular = loc("u_Light0Specular");
locLight1Ambient = loc("u_Light1Ambient");
locLight1Diffuse = loc("u_Light1Diffuse");
locLight1Specular = loc("u_Light1Specular");
// Pre-computed path
locSceneColor = loc("u_SceneColor");
locLightProd0Ambient = loc("u_LightProd0Ambient");
locLightProd0Diffuse = loc("u_LightProd0Diffuse");
locLightProd0Specular = loc("u_LightProd0Specular");
locLightProd1Ambient = loc("u_LightProd1Ambient");
locLightProd1Diffuse = loc("u_LightProd1Diffuse");
locLightProd1Specular = loc("u_LightProd1Specular");
// Light positions
locLight0Position = loc("u_Light0Position");
locLight1Position = loc("u_Light1Position");
locNormalScale = loc("u_NormalScale");
// TexGen
locTexGenObjPlaneS = loc("u_TexGenObjPlaneS");
locTexGenObjPlaneT = loc("u_TexGenObjPlaneT");
locTexGenObjPlaneR = loc("u_TexGenObjPlaneR");
locTexGenObjPlaneQ = loc("u_TexGenObjPlaneQ");
locTexGenEyePlaneS = loc("u_TexGenEyePlaneS");
locTexGenEyePlaneT = loc("u_TexGenEyePlaneT");
locTexGenEyePlaneR = loc("u_TexGenEyePlaneR");
locTexGenEyePlaneQ = loc("u_TexGenEyePlaneQ");
// Clip planes
locClipPlanes = loc("u_ClipPlane[0]");
// Wide line emulation
locViewportSize = loc("u_ViewportSize");
locLineWidth = loc("u_LineWidth");
// Fragment
for (int i = 0; i < 4; i++) {
locSampler[i] = loc("u_Sampler" + i);
locSampler[i] = RENDER_BACKEND.getUniformLocation(programId, "u_Sampler" + i);
}
locAlphaRef = loc("u_AlphaRef");
for (int i = 0; i < 4; i++) {
locTexEnvColor[i] = loc("u_TexEnvColor" + i);
}
locFogParams = loc("u_FogParams");
locFogColor = loc("u_FogColor");
}
private int loc(String name) {
return RENDER_BACKEND.getUniformLocation(programId, name);
}
public void destroy() {
RENDER_BACKEND.deleteProgram(programId);
GLStateManager.glDeleteProgram(programId);
}
/**
@@ -216,7 +83,7 @@ public class Program {
final String debugName = "FFP(v=0x" + vkHex + ",f=" + id + (hasGeom ? ",g" : "") + ")";
GLDebug.nameObject(KHRDebug.GL_PROGRAM, program, debugName);
// Detach and delete individual shaders — they're linked into the program
// Detach and delete individual shaders - they're linked into the program
for (int i = 0; i < shaderCount; i++) {
backend.detachShader(program, shaders[i]);
backend.deleteShader(shaders[i]);
@@ -225,6 +92,11 @@ public class Program {
final Program ffpProgram = new Program(program, vk, fk);
final int blockIndex = backend.getUniformBlockIndex(program, FFPUniformBlock.BLOCK_NAME);
if (blockIndex != GL31.GL_INVALID_INDEX) {
backend.uniformBlockBinding(program, blockIndex, FFPUniformBlock.BINDING_POINT);
}
final int previousProgram = GLStateManager.getActiveProgram();
backend.useProgram(program);
for (int i = 0; i < 4; i++) {
@@ -243,6 +115,10 @@ public class Program {
private static int compileShader(int type, String src, String name) {
final RenderBackend backend = RENDER_BACKEND;
final int shader = backend.createShader(type);
if (RenderSystem.isGLES()) {
src = CompatShaderTransformer.toGLES(src, type, type == GL20.GL_FRAGMENT_SHADER);
dumpTranslatedFFPShader(type, src, name);
}
backend.shaderSource(shader, src);
backend.compileShader(shader);
@@ -271,4 +147,22 @@ public class Program {
return shader;
}
// Post-translation GLSL ES dump, paired with ShaderCache's pre-translation 330 core dump.
private static void dumpTranslatedFFPShader(int type, String translatedSrc, String name) {
final Path dir = SystemProperties.shaderDumpDir("ffp");
if (dir == null) return;
final String suffix = switch (type) {
case GL20.GL_VERTEX_SHADER -> ".vert.glsl";
case GL20.GL_FRAGMENT_SHADER -> ".frag.glsl";
case GL32.GL_GEOMETRY_SHADER -> ".geom.glsl";
default -> ".glsl";
};
try {
Files.createDirectories(dir);
Files.writeString(dir.resolve(name + ".gles" + suffix), translatedSrc);
} catch (IOException e) {
GLStateManager.LOGGER.warn("Failed to dump translated FFP shader: {}", e.getMessage());
}
}
}
@@ -3,6 +3,7 @@ package com.gtnewhorizons.angelica.glsm.ffp;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import lombok.Setter;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.profiling.Tracy;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@@ -25,6 +26,8 @@ import java.util.IdentityHashMap;
* </pre>
*/
public class ShaderCache {
private static final Tracy.ZoneId Z_FFP_SHADER_GEN = Tracy.zoneId("ffpShaderGen", Tracy.COLOR_FFP);
private static final int SLOT_STRIDE = 6;
private static final int INITIAL_CAPACITY = 64; // power of 2
private static final long EMPTY_MARKER = 0L; // slot [1] == 0 means empty (fkLen >= 1 guarantees nonzero)
@@ -98,18 +101,25 @@ public class ShaderCache {
}
final VertexKey vk = VertexKey.fromPacked(vkPacked);
final String vertSrc = getOrGenerateVertex(vk);
final FragmentKey fk = FragmentKey.fromPacked(fkPacked, fkLen);
String fragSrc = findFragSource(fkPacked, fkLen);
if (fragSrc == null) {
fragSrc = FragmentShaderGenerator.generate(fk);
uniqueFragCount++;
final String vertSrc;
String fragSrc;
final String geomSrc;
final Program program;
if (Tracy.ENABLED) Tracy.beginZone(Z_FFP_SHADER_GEN);
try {
vertSrc = getOrGenerateVertex(vk);
fragSrc = findFragSource(fkPacked, fkLen);
if (fragSrc == null) {
fragSrc = FragmentShaderGenerator.generate(fk);
uniqueFragCount++;
}
geomSrc = vk.wideLineEmulation() ? getOrGenerateGeometry(vk) : null;
program = Program.create(vk, fk, vertSrc, fragSrc, geomSrc);
} finally {
if (Tracy.ENABLED) Tracy.endZone();
}
final String geomSrc = vk.wideLineEmulation() ? getOrGenerateGeometry(vk) : null;
final Program program = Program.create(vk, fk, vertSrc, fragSrc, geomSrc);
final int insertIdx = findEmptySlot(hash);
final int base = insertIdx * SLOT_STRIDE;
keys[base] = vkPacked;
@@ -2,19 +2,21 @@ 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.config.SystemProperties;
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.profiling.Tracy;
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;
import java.nio.file.Paths;
import java.util.Arrays;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
@@ -22,7 +24,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,30 +33,43 @@ 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;
private int preDrawCalls;
private int lastFramePreDrawCalls;
public static long variantSwitches;
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"));
}
cache.setDumpDir(SystemProperties.shaderDumpDir("ffp"));
VertexFormat.registerSetupBufferStateOverride((format, offset) -> {
VAOManager.setCurrentVertexFlags(format.getVertexFlags());
return false;
});
}
public static ShaderManager getInstance() {
@@ -62,54 +77,76 @@ 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;
GLStateManager.forceAttribDefaultsDirty();
}
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;
preDrawCalls++;
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,81 +155,80 @@ 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) {
if (Tracy.ENABLED) variantSwitches++;
RENDER_BACKEND.useProgram(programId);
lastBoundProgramId = programId;
}
}
private void uploadUniforms() {
if (currentProgram != null) {
uniforms.upload(currentProgram);
uniforms.upload();
}
}
public static void endFrame() {
final ShaderManager sm = Holder.INSTANCE;
sm.lastFramePreDrawCalls = sm.preDrawCalls;
sm.preDrawCalls = 0;
sm.uniforms.endFrame();
}
public static void setCurrentNormal(float x, float y, float z) {
currentNormal.set(x, y, z);
normalGeneration++;
}
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();
final GLSMInitConfig config = GLStateManager.getInitConfig();
if (config != null && config.getStreamingDrawerDestroy() != null) config.getStreamingDrawerDestroy().run();
com.gtnewhorizons.angelica.glsm.QuadConverter.destroy();
QuadConverter.destroy();
active = false;
currentProgram = null;
}
public String getDebugInfo() {
return String.format("FFP: %d programs (%d vert, %d frag variants)",
cache.getProgramCount(), cache.getVertexVariantCount(), cache.getFragmentVariantCount());
return String.format("FFP: %d programs (%d vert, %d frag variants) | UBO: %d/%d draws wrote (mat %d, light %d, frag %d, misc %d)",
cache.getProgramCount(), cache.getVertexVariantCount(), cache.getFragmentVariantCount(),
uniforms.lastFrameBlockWrites, lastFramePreDrawCalls,
uniforms.lastFrameStagedMatrices, uniforms.lastFrameStagedLighting,
uniforms.lastFrameStagedFragment, uniforms.lastFrameStagedMisc);
}
public int statLastFramePreDrawCalls() { return lastFramePreDrawCalls; }
public int statLastFrameBlockWrites() { return uniforms.lastFrameBlockWrites; }
public int statLastFrameBlockSkips() { return uniforms.lastFrameBlockSkips; }
public int statLastFrameStagedMatrices() { return uniforms.lastFrameStagedMatrices; }
public int statLastFrameStagedLighting() { return uniforms.lastFrameStagedLighting; }
public int statLastFrameStagedFragment() { return uniforms.lastFrameStagedFragment; }
public int statLastFrameStagedColor() { return uniforms.lastFrameStagedColor; }
public int statLastFrameStagedNormal() { return uniforms.lastFrameStagedNormal; }
public int statLastFrameStagedTexCoord() { return uniforms.lastFrameStagedTexCoord; }
public int statLastFrameStagedLightmap() { return uniforms.lastFrameStagedLightmap; }
public int statLastFrameStagedTexGen() { return uniforms.lastFrameStagedTexGen; }
public int statLastFrameStagedClipPlanes() { return uniforms.lastFrameStagedClipPlanes; }
public int statLastFrameStagedMisc() { return uniforms.lastFrameStagedMisc; }
public int statProgramCount() { return cache.getProgramCount(); }
}
@@ -4,491 +4,438 @@ import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.states.ClipPlaneState;
import com.gtnewhorizons.angelica.glsm.states.FogState;
import com.gtnewhorizons.angelica.glsm.states.LightState;
import com.gtnewhorizons.angelica.glsm.states.LineState;
import com.gtnewhorizons.angelica.glsm.states.MaterialState;
import com.gtnewhorizons.angelica.glsm.states.TexGenState;
import com.gtnewhorizons.angelica.glsm.states.ViewportState;
import com.gtnewhorizons.angelica.glsm.streaming.UniformRingBuffer;
import com.gtnewhorizons.angelica.glsm.hooks.GLSMConfig;
import org.joml.Math;
import org.joml.Matrix3f;
import org.joml.Matrix4f;
import org.joml.Vector3f;
import org.joml.Vector4f;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL31;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAllocFloat;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCalloc;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
/**
* Uploads GLSM cached state to the active FFP program's uniforms.
*
* Follows Mesa's prog_statevars.c patterns:
* - Pre-computed derived state (light products, scene color) when material is static
* - Raw light/material values when color material is active
* - Optimized fog params (pre-divided for single MAD in shader)
* Maintains the shared {@link FFPUniformBlock} image from GLSM cached state and streams it to the UBO ring.
*/
public class Uniforms {
// Reusable temp buffers for uniform upload
private final FloatBuffer mat4Buf = memAllocFloat(16);
private final FloatBuffer mat3Buf = memAllocFloat(9);
private final FloatBuffer vec4Buf = memAllocFloat(4);
private final FloatBuffer vec3Buf = memAllocFloat(3);
private final FloatBuffer clipPlaneBuf = memAllocFloat(32); // 8 planes * vec4
// Derived matrices (computed on CPU)
private static final int RING_CAPACITY = 16 * 1024 * 1024;
private final ByteBuffer staging = memCalloc(FFPUniformBlock.SIZE);
private final long stagingAddress = memAddress0(staging);
private UniformRingBuffer ring;
private boolean bound;
private boolean contentChanged;
private int mvGen = -1, mvLinearGen = -1, projGen = -1, texMatGen = -1;
private int lightingGen = -1, fragmentGen = -1, colorGen = -1, normalGen = -1, texCoordGen = -1;
private int texGenGen = -1, clipPlaneGen = -1;
private float lightmapX = Float.NaN, lightmapY = Float.NaN, lineWidth = Float.NaN;
private int viewportX = Integer.MIN_VALUE, viewportY = Integer.MIN_VALUE, viewportWidth = -1, viewportHeight = -1;
private int lineStipple = -1;
int blockWrites;
int blockSkips;
int stagedMatrices;
int stagedLighting;
int stagedFragment;
int stagedColor;
int stagedNormal;
int stagedTexCoord;
int stagedLightmap;
int stagedTexGen;
int stagedClipPlanes;
int stagedMisc;
int lastFrameBlockWrites;
int lastFrameBlockSkips;
int lastFrameStagedMatrices;
int lastFrameStagedLighting;
int lastFrameStagedFragment;
int lastFrameStagedColor;
int lastFrameStagedNormal;
int lastFrameStagedTexCoord;
int lastFrameStagedLightmap;
int lastFrameStagedTexGen;
int lastFrameStagedClipPlanes;
int lastFrameStagedMisc;
private final Matrix4f mvpMatrix = new Matrix4f();
private final Matrix3f normalMatrix = new Matrix3f();
private final Vector3f tempVec3 = new Vector3f();
private final FloatBuffer clipPlaneBuf = memAllocFloat(32); // 8 planes * vec4
// Temp vectors for light product and normal scale computation
private final Vector4f tempVec4 = new Vector4f();
private final org.joml.Vector3f tempVec3 = new org.joml.Vector3f();
private static final double LN2 = java.lang.Math.log(2.0);
private static final double SQRT_LN2 = java.lang.Math.sqrt(LN2);
// 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);
public void upload() {
boolean dirty = false;
contentChanged = false;
// 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;
/**
* 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 int mvGen = GLStateManager.mvGeneration;
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;
if (mvChanged || projChanged || texMatChanged) {
uploadMatrices(program, mvChanged, projChanged, texMatChanged);
lastMvGen = mvGen;
lastProjGen = projGen;
lastTexMatGen = texMatGen;
final int mvG = GLStateManager.getMvGeneration();
final int projG = GLStateManager.getProjGeneration();
final boolean mvChanged = mvG != mvGen;
final boolean projChanged = projG != projGen;
if (mvChanged || projChanged) {
stageMatrices(mvChanged, projChanged);
mvGen = mvG;
projGen = projG;
stagedMatrices++;
dirty = true;
}
final int mvLinG = GLStateManager.getMvLinearGeneration();
if (mvLinG != mvLinearGen) {
stageNormalMatrix();
mvLinearGen = mvLinG;
dirty = true;
}
final int texMatG = GLStateManager.getTexMatrixGeneration();
if (texMatG != texMatGen) {
stageTextureMatrices();
texMatGen = texMatG;
dirty = true;
}
final int litG = GLStateManager.getLightingGeneration();
if (litG != lightingGen) {
stageLighting();
lightingGen = litG;
stagedLighting++;
dirty = true;
}
final int colG = GLStateManager.getColorGeneration();
if (colG != colorGen) {
stageCurrentColor();
colorGen = colG;
stagedColor++;
dirty = true;
}
final int normG = ShaderManager.getNormalGeneration();
if (normG != normalGen) {
final Vector3f normal = ShaderManager.getCurrentNormal();
putVec3(FFPUniformBlock.CURRENT_NORMAL, normal.x, normal.y, normal.z);
normalGen = normG;
stagedNormal++;
dirty = true;
}
final int tcG = ShaderManager.getTexCoordGeneration();
if (tcG != texCoordGen) {
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_0, ShaderManager.getCurrentTexCoord(0));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_2, ShaderManager.getCurrentTexCoord(2));
putVec4(FFPUniformBlock.CURRENT_TEX_COORD_3, ShaderManager.getCurrentTexCoord(3));
texCoordGen = tcG;
stagedTexCoord++;
dirty = true;
}
final float brightX = GLSMConfig.lastBrightnessX;
final float brightY = GLSMConfig.lastBrightnessY;
if (brightX != lightmapX || brightY != lightmapY) {
putFloat(FFPUniformBlock.CURRENT_LIGHTMAP_COORD, brightX);
putFloat(FFPUniformBlock.CURRENT_LIGHTMAP_COORD + 4, brightY);
lightmapX = brightX;
lightmapY = brightY;
stagedLightmap++;
dirty = true;
}
final int tgG = GLStateManager.getTexGenGeneration();
if (tgG != texGenGen) {
stageTexGen();
texGenGen = tgG;
stagedTexGen++;
dirty = true;
}
final int cpG = GLStateManager.getClipPlaneGeneration();
if (cpG != clipPlaneGen) {
stageClipPlanes();
clipPlaneGen = cpG;
stagedClipPlanes++;
dirty = true;
}
final int fragG = GLStateManager.getFragmentGeneration();
if (fragG != fragmentGen) {
stageFragment();
fragmentGen = fragG;
stagedFragment++;
dirty = true;
}
final float lw = GLStateManager.getLineState().getWidth();
if (lw != lineWidth) {
putFloat(FFPUniformBlock.LINE_WIDTH, lw);
lineWidth = lw;
stagedMisc++;
dirty = true;
}
final ViewportState vp = GLStateManager.getViewportState();
if (vp.x != viewportX || vp.y != viewportY || vp.width != viewportWidth || vp.height != viewportHeight) {
putFloat(FFPUniformBlock.VIEWPORT_SIZE, vp.width);
putFloat(FFPUniformBlock.VIEWPORT_SIZE + 4, vp.height);
putVec4(FFPUniformBlock.VIEWPORT, vp.x, vp.y, vp.width, vp.height);
viewportX = vp.x;
viewportY = vp.y;
viewportWidth = vp.width;
viewportHeight = vp.height;
stagedMisc++;
dirty = true;
}
final LineState line = GLStateManager.getLineState();
final int stippleFactor = line.getStippleFactor() < 1 ? 1 : line.getStippleFactor();
final int stipplePacked = (line.getStipplePattern() & 0xFFFF) | (stippleFactor << 16);
if (stipplePacked != lineStipple) {
putInt(FFPUniformBlock.LINE_STIPPLE, stipplePacked);
lineStipple = stipplePacked;
stagedMisc++;
dirty = true;
}
if (program.getVertexKey().lightingEnabled()) {
final int litGen = GLStateManager.lightingGeneration;
if (programChanged || litGen != lastLightingGen) {
uploadLighting(program);
lastLightingGen = litGen;
if (dirty || !bound) {
if (!contentChanged && bound) {
blockSkips++;
return;
}
}
// Current color/normal/texcoord — skip if generation unchanged
if (!program.getVertexKey().hasVertexColor()) {
final int colorGen = GLStateManager.colorGeneration;
if (programChanged || colorGen != lastColorGen) {
uploadCurrentColor(program);
lastColorGen = colorGen;
if (ring == null) {
ring = new UniformRingBuffer(RING_CAPACITY, FFPUniformBlock.SIZE);
}
}
if (!program.getVertexKey().hasVertexNormal() && program.getVertexKey().lightingEnabled()) {
final int normalGen = ShaderManager.getNormalGeneration();
if (programChanged || normalGen != lastNormalGen) {
uploadCurrentNormal(program);
lastNormalGen = normalGen;
}
}
if (!program.getVertexKey().hasVertexTexCoord() && program.getVertexKey().textureEnabled()) {
final int texGen = ShaderManager.getTexCoordGeneration();
if (programChanged || texGen != lastTexCoordGen) {
uploadCurrentTexCoord(program);
lastTexCoordGen = texGen;
}
}
if (program.getVertexKey().lightmapEnabled() && !program.getVertexKey().hasVertexLightmap()) {
if (programChanged || GLSMConfig.lastBrightnessX != lastLightmapX || GLSMConfig.lastBrightnessY != lastLightmapY) {
uploadCurrentLightmapCoord(program);
lastLightmapX = GLSMConfig.lastBrightnessX;
lastLightmapY = GLSMConfig.lastBrightnessY;
}
}
if (program.getVertexKey().texGenEnabled()) {
final int tgGen = GLStateManager.texGenGeneration;
if (programChanged || tgGen != lastTexGenGen) {
uploadTexGen(program);
lastTexGenGen = tgGen;
}
}
if (program.getVertexKey().clipPlanesEnabled()) {
final int cpGen = GLStateManager.clipPlaneGeneration;
if (programChanged || cpGen != lastClipPlaneGen) {
uploadClipPlanes(program);
lastClipPlaneGen = cpGen;
}
}
final int fragGen = GLStateManager.fragmentGeneration;
if (programChanged || fragGen != lastFragmentGen) {
uploadFragmentUniforms(program);
lastFragmentGen = fragGen;
}
// Wide line emulation uniforms
if (program.locLineWidth != -1 && program.locViewportSize != -1) {
uploadWideLineUniforms(program, programChanged);
final int offset = ring.writeBlock(staging);
RENDER_BACKEND.bindBufferRange(GL31.GL_UNIFORM_BUFFER, FFPUniformBlock.BINDING_POINT,
ring.getBufferId(), offset, FFPUniformBlock.SIZE);
bound = true;
blockWrites++;
}
}
private void uploadMatrices(Program program, boolean mvChanged, boolean projChanged, boolean texMatChanged) {
private void stageMatrices(boolean mvChanged, boolean projChanged) {
final Matrix4f mv = GLStateManager.getModelViewMatrix();
final Matrix4f proj = GLStateManager.getProjectionMatrix();
// ModelView + derived (normal matrix, normal scale)
if (mvChanged) {
if (program.locModelViewMatrix != -1) {
mv.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locModelViewMatrix, false, mat4Buf);
}
// Normal matrix = inverse transpose of upper-left 3x3 of ModelView
if (program.locNormalMatrix != -1 || program.locNormalScale != -1) {
mv.normal(normalMatrix);
}
if (program.locNormalMatrix != -1) {
normalMatrix.get(mat3Buf);
RENDER_BACKEND.uniformMatrix3(program.locNormalMatrix, false, mat3Buf);
}
// Normal scale (for GL_RESCALE_NORMAL without GL_NORMALIZE)
if (program.locNormalScale != -1) {
// Scale factor = 1/length of first column of normal matrix
final float scale = 1.0f / normalMatrix.getColumn(0, tempVec3).length();
RENDER_BACKEND.uniform1f(program.locNormalScale, scale);
}
putMat4(FFPUniformBlock.MODEL_VIEW_MATRIX, mv);
}
// Projection
if (projChanged) {
if (program.locProjectionMatrix != -1) {
proj.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locProjectionMatrix, false, mat4Buf);
}
}
// MVP = Projection * ModelView — needs recompute if either input changed
if ((mvChanged || projChanged) && program.locMVPMatrix != -1) {
proj.mul(mv, mvpMatrix);
mvpMatrix.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locMVPMatrix, false, mat4Buf);
}
// Texture matrices
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);
}
// Texture matrix unit 1 (lightmap)
if (program.locLightmapTextureMatrix != -1) {
final Matrix4f lmTexMat = GLStateManager.getTextures().getTextureUnitMatrix(1);
lmTexMat.get(mat4Buf);
RENDER_BACKEND.uniformMatrix4(program.locLightmapTextureMatrix, false, mat4Buf);
}
putMat4(FFPUniformBlock.PROJECTION_MATRIX, proj);
}
proj.mul(mv, mvpMatrix);
putMat4(FFPUniformBlock.MVP_MATRIX, mvpMatrix);
}
private void uploadLighting(Program program) {
final VertexKey vk = program.getVertexKey();
if (vk.colorMaterialEnabled()) {
uploadLightingColorMaterial(program, vk);
} else {
uploadLightingPreComputed(program, vk);
}
private void stageNormalMatrix() {
GLStateManager.getModelViewMatrix().normal(normalMatrix);
putMat3(FFPUniformBlock.NORMAL_MATRIX, normalMatrix);
final float scale = 1.0f / normalMatrix.getColumn(0, tempVec3).length();
putFloat(FFPUniformBlock.NORMAL_SCALE, scale);
}
/**
* Color material active: upload raw light and material values.
* The shader multiplies them with vertex color at runtime.
*/
private void uploadLightingColorMaterial(Program program, VertexKey vk) {
private void stageTextureMatrices() {
putMat4(FFPUniformBlock.TEXTURE_MATRIX_0, GLStateManager.getTextures().getTextureUnitMatrix(0));
putMat4(FFPUniformBlock.TEXTURE_MATRIX_2, GLStateManager.getTextures().getTextureUnitMatrix(2));
putMat4(FFPUniformBlock.TEXTURE_MATRIX_3, GLStateManager.getTextures().getTextureUnitMatrix(3));
putMat4(FFPUniformBlock.LIGHTMAP_TEXTURE_MATRIX, GLStateManager.getTextures().getTextureUnitMatrix(1));
}
private void stageLighting() {
final MaterialState mat = GLStateManager.getFrontMaterial();
final Vector4f lmAmbient = GLStateManager.getLightModel().ambient;
final LightState light0 = GLStateManager.getLightDataStates()[0];
final LightState light1 = GLStateManager.getLightDataStates()[1];
uploadVec4(program.locLightModelAmbient, GLStateManager.getLightModel().ambient);
uploadVec4(program.locMaterialEmission, mat.emission);
uploadVec4(program.locMaterialAmbient, mat.ambient);
uploadVec4(program.locMaterialDiffuse, mat.diffuse);
uploadVec4(program.locMaterialSpecular, mat.specular);
if (program.locMaterialShininess != -1) {
RENDER_BACKEND.uniform1f(program.locMaterialShininess, mat.shininess);
}
putVec4(FFPUniformBlock.LIGHT_MODEL_AMBIENT, lmAmbient);
putVec4(FFPUniformBlock.MATERIAL_EMISSION, mat.emission);
putVec4(FFPUniformBlock.MATERIAL_AMBIENT, mat.ambient);
putVec4(FFPUniformBlock.MATERIAL_DIFFUSE, mat.diffuse);
putVec4(FFPUniformBlock.MATERIAL_SPECULAR, mat.specular);
putFloat(FFPUniformBlock.MATERIAL_SHININESS, mat.shininess);
putVec4(FFPUniformBlock.LIGHT0_AMBIENT, light0.ambient);
putVec4(FFPUniformBlock.LIGHT0_DIFFUSE, light0.diffuse);
putVec4(FFPUniformBlock.LIGHT0_SPECULAR, light0.specular);
putVec4(FFPUniformBlock.LIGHT0_POSITION, light0.position);
putVec4(FFPUniformBlock.LIGHT1_AMBIENT, light1.ambient);
putVec4(FFPUniformBlock.LIGHT1_DIFFUSE, light1.diffuse);
putVec4(FFPUniformBlock.LIGHT1_SPECULAR, light1.specular);
putVec4(FFPUniformBlock.LIGHT1_POSITION, light1.position);
if (vk.light0Enabled()) {
final LightState light0 = GLStateManager.getLightDataStates()[0];
uploadVec4(program.locLight0Ambient, light0.ambient);
uploadVec4(program.locLight0Diffuse, light0.diffuse);
uploadVec4(program.locLight0Specular, light0.specular);
uploadVec4(program.locLight0Position, light0.position);
}
if (vk.light1Enabled()) {
final LightState light1 = GLStateManager.getLightDataStates()[1];
uploadVec4(program.locLight1Ambient, light1.ambient);
uploadVec4(program.locLight1Diffuse, light1.diffuse);
uploadVec4(program.locLight1Specular, light1.specular);
uploadVec4(program.locLight1Position, light1.position);
}
putVec4(FFPUniformBlock.SCENE_COLOR,
mat.emission.x + mat.ambient.x * lmAmbient.x,
mat.emission.y + mat.ambient.y * lmAmbient.y,
mat.emission.z + mat.ambient.z * lmAmbient.z,
mat.diffuse.w);
putLightProduct(FFPUniformBlock.LIGHT_PROD0_AMBIENT, light0.ambient, mat.ambient);
putLightProduct(FFPUniformBlock.LIGHT_PROD0_DIFFUSE, light0.diffuse, mat.diffuse);
putLightProduct(FFPUniformBlock.LIGHT_PROD0_SPECULAR, light0.specular, mat.specular);
putLightProduct(FFPUniformBlock.LIGHT_PROD1_AMBIENT, light1.ambient, mat.ambient);
putLightProduct(FFPUniformBlock.LIGHT_PROD1_DIFFUSE, light1.diffuse, mat.diffuse);
putLightProduct(FFPUniformBlock.LIGHT_PROD1_SPECULAR, light1.specular, mat.specular);
}
/**
* No color material: upload pre-computed light products and scene color.
* Fewer shader multiplies since material is static.
*/
private void uploadLightingPreComputed(Program program, VertexKey vk) {
final MaterialState mat = GLStateManager.getFrontMaterial();
// Scene color = emission + ambient * lightModel.ambient
// Alpha = material.diffuse.a
if (program.locSceneColor != -1) {
tempVec4.set(
mat.emission.x + mat.ambient.x * GLStateManager.getLightModel().ambient.x,
mat.emission.y + mat.ambient.y * GLStateManager.getLightModel().ambient.y,
mat.emission.z + mat.ambient.z * GLStateManager.getLightModel().ambient.z,
mat.diffuse.w // alpha from diffuse
);
uploadVec4(program.locSceneColor, tempVec4);
}
if (program.locMaterialShininess != -1) {
RENDER_BACKEND.uniform1f(program.locMaterialShininess, mat.shininess);
}
// Per-light products
if (vk.light0Enabled()) {
final LightState light0 = GLStateManager.getLightDataStates()[0];
uploadVec4(program.locLight0Position, light0.position);
uploadLightProduct(program.locLightProd0Ambient, light0.ambient, mat.ambient);
uploadLightProduct(program.locLightProd0Diffuse, light0.diffuse, mat.diffuse);
uploadLightProduct(program.locLightProd0Specular, light0.specular, mat.specular);
}
if (vk.light1Enabled()) {
final LightState light1 = GLStateManager.getLightDataStates()[1];
uploadVec4(program.locLight1Position, light1.position);
uploadLightProduct(program.locLightProd1Ambient, light1.ambient, mat.ambient);
uploadLightProduct(program.locLightProd1Diffuse, light1.diffuse, mat.diffuse);
uploadLightProduct(program.locLightProd1Specular, light1.specular, mat.specular);
}
private void putLightProduct(int offset, Vector4f lightVal, Vector4f materialVal) {
putVec3(offset, lightVal.x * materialVal.x, lightVal.y * materialVal.y, lightVal.z * materialVal.z);
}
/**
* Upload pre-multiplied light product: light.X * material.X (RGB only).
*/
private void uploadLightProduct(int loc, Vector4f lightVal, Vector4f materialVal) {
if (loc == -1) return;
vec3Buf.clear();
vec3Buf.put(lightVal.x * materialVal.x);
vec3Buf.put(lightVal.y * materialVal.y);
vec3Buf.put(lightVal.z * materialVal.z);
vec3Buf.flip();
RENDER_BACKEND.uniform3(loc, vec3Buf);
}
private void uploadCurrentColor(Program program) {
if (program.locCurrentColor == -1) return;
// Upload the current color from GLSM
private void stageCurrentColor() {
final var color = GLStateManager.getColor();
vec4Buf.clear();
vec4Buf.put(color.getRed());
vec4Buf.put(color.getGreen());
vec4Buf.put(color.getBlue());
vec4Buf.put(color.getAlpha());
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locCurrentColor, vec4Buf);
putVec4(FFPUniformBlock.CURRENT_COLOR,
Math.clamp(0f, 1f, color.getRed()),
Math.clamp(0f, 1f, color.getGreen()),
Math.clamp(0f, 1f, color.getBlue()),
Math.clamp(0f, 1f, color.getAlpha()));
}
private void uploadCurrentTexCoord(Program program) {
if (program.locCurrentTexCoord == -1) return;
final var tc = ShaderManager.getCurrentTexCoord();
uploadVec4(program.locCurrentTexCoord, tc);
}
private void uploadCurrentLightmapCoord(Program program) {
if (program.locCurrentLightmapCoord == -1) return;
RENDER_BACKEND.uniform2f(program.locCurrentLightmapCoord,
GLSMConfig.lastBrightnessX, GLSMConfig.lastBrightnessY);
}
private void uploadCurrentNormal(Program program) {
if (program.locCurrentNormal == -1) return;
// 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();
RENDER_BACKEND.uniform3(program.locCurrentNormal, vec3Buf);
}
private void uploadTexGen(Program program) {
final VertexKey vk = program.getVertexKey();
private void stageTexGen() {
final TexGenState tg = GLStateManager.getTextures().getTexGenState(0);
if (vk.texGenModeS() == VertexKey.TG_OBJ_LINEAR) {
uploadPlane(program.locTexGenObjPlaneS, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_S));
} else if (vk.texGenModeS() == VertexKey.TG_EYE_LINEAR) {
uploadPlane(program.locTexGenEyePlaneS, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_S));
}
if (vk.texGenModeT() == VertexKey.TG_OBJ_LINEAR) {
uploadPlane(program.locTexGenObjPlaneT, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_T));
} else if (vk.texGenModeT() == VertexKey.TG_EYE_LINEAR) {
uploadPlane(program.locTexGenEyePlaneT, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_T));
}
if (vk.texGenModeR() == VertexKey.TG_OBJ_LINEAR) {
uploadPlane(program.locTexGenObjPlaneR, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_R));
} else if (vk.texGenModeR() == VertexKey.TG_EYE_LINEAR) {
uploadPlane(program.locTexGenEyePlaneR, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_R));
}
if (vk.texGenModeQ() == VertexKey.TG_OBJ_LINEAR) {
uploadPlane(program.locTexGenObjPlaneQ, tg.getObjectPlane(org.lwjgl.opengl.GL11.GL_Q));
} else if (vk.texGenModeQ() == VertexKey.TG_EYE_LINEAR) {
uploadPlane(program.locTexGenEyePlaneQ, tg.getEyePlane(org.lwjgl.opengl.GL11.GL_Q));
}
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_S, tg.getObjectPlane(GL11.GL_S));
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_T, tg.getObjectPlane(GL11.GL_T));
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_R, tg.getObjectPlane(GL11.GL_R));
putPlane(FFPUniformBlock.TEX_GEN_OBJ_PLANE_Q, tg.getObjectPlane(GL11.GL_Q));
putPlane(FFPUniformBlock.TEX_GEN_EYE_PLANE_S, tg.getEyePlane(GL11.GL_S));
putPlane(FFPUniformBlock.TEX_GEN_EYE_PLANE_T, tg.getEyePlane(GL11.GL_T));
putPlane(FFPUniformBlock.TEX_GEN_EYE_PLANE_R, tg.getEyePlane(GL11.GL_R));
putPlane(FFPUniformBlock.TEX_GEN_EYE_PLANE_Q, tg.getEyePlane(GL11.GL_Q));
}
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.flip();
RENDER_BACKEND.uniform4(loc, vec4Buf);
}
private void uploadClipPlanes(Program program) {
if (program.locClipPlanes == -1) return;
private void stageClipPlanes() {
final ClipPlaneState cps = GLStateManager.getClipPlaneState();
clipPlaneBuf.clear();
for (int i = 0; i < GLStateManager.MAX_CLIP_PLANES; i++) {
cps.putEyePlane(i, clipPlaneBuf);
}
clipPlaneBuf.flip();
RENDER_BACKEND.uniform4(program.locClipPlanes, clipPlaneBuf);
final long src = memAddress0(clipPlaneBuf);
for (int i = 0; i < 32 * 4; i += 4) {
putInt(FFPUniformBlock.CLIP_PLANES + i, memGetInt(src + i));
}
}
private void uploadFragmentUniforms(Program program) {
final FragmentKey fk = program.getFragmentKey();
private void stageFragment() {
putFloat(FFPUniformBlock.ALPHA_REF, GLStateManager.getAlphaState().getReference());
// Alpha test reference
if (fk.alphaTestEnabled() && program.locAlphaRef != -1) {
RENDER_BACKEND.uniform1f(program.locAlphaRef, GLStateManager.getAlphaState().getReference());
}
// Per-unit tex env color
for (int i = 0; i < 4; i++) {
if (program.locTexEnvColor[i] != -1) {
final var envState = GLStateManager.getTextures().getTexEnvState(i);
vec4Buf.clear();
vec4Buf.put(envState.envColorR);
vec4Buf.put(envState.envColorG);
vec4Buf.put(envState.envColorB);
vec4Buf.put(envState.envColorA);
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locTexEnvColor[i], vec4Buf);
}
final var envState = GLStateManager.getTextures().getTexEnvState(i);
putVec4(FFPUniformBlock.TEX_ENV_COLOR_0 + i * 16,
envState.envColorR, envState.envColorG, envState.envColorB, envState.envColorA);
}
// Fog
if (fk.fogMode() != FragmentKey.FOG_NONE) {
uploadFog(program);
}
}
putVec4(FFPUniformBlock.OVERLAY_COLOR,
GLStateManager.getOverlayR(), GLStateManager.getOverlayG(),
GLStateManager.getOverlayB(), GLStateManager.getOverlayA());
/**
* Upload optimized fog params (following Mesa's STATE_FOG_PARAMS_OPTIMIZED):
*/
private void uploadFog(Program program) {
// Mesa STATE_FOG_PARAMS_OPTIMIZED
final FogState fog = GLStateManager.getFogState();
if (program.locFogParams != -1) {
final float start = fog.getStart();
final float end = fog.getEnd();
final float density = fog.getDensity();
final float range = end - start;
vec4Buf.clear();
vec4Buf.put(range != 0.0f ? -1.0f / range : 0.0f); // [0]: -1/(end-start)
vec4Buf.put(range != 0.0f ? end / range : 1.0f); // [1]: end/(end-start)
vec4Buf.put((float)(density / LN2)); // [2]: density/ln(2)
vec4Buf.put((float)(density / SQRT_LN2)); // [3]: density/sqrt(ln(2))
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locFogParams, vec4Buf);
}
if (program.locFogColor != -1) {
vec4Buf.clear();
vec4Buf.put((float) fog.getFogColor().x);
vec4Buf.put((float) fog.getFogColor().y);
vec4Buf.put((float) fog.getFogColor().z);
vec4Buf.put(fog.getFogAlpha());
vec4Buf.flip();
RENDER_BACKEND.uniform4(program.locFogColor, vec4Buf);
}
final float start = fog.getStart();
final float end = fog.getEnd();
final float density = fog.getDensity();
final float range = end - start;
putVec4(FFPUniformBlock.FOG_PARAMS,
range != 0.0f ? -1.0f / range : 0.0f,
range != 0.0f ? end / range : 1.0f,
(float) (density / LN2),
(float) (density / SQRT_LN2));
putVec4(FFPUniformBlock.FOG_COLOR,
(float) fog.getFogColor().x, (float) fog.getFogColor().y, (float) fog.getFogColor().z,
fog.getFogAlpha());
}
private void uploadWideLineUniforms(Program program, boolean programChanged) {
final float lineWidth = GLStateManager.getLineState().getWidth();
if (programChanged || lineWidth != lastLineWidth) {
RENDER_BACKEND.uniform1f(program.locLineWidth, lineWidth);
lastLineWidth = lineWidth;
}
final int vw = GLStateManager.getViewportState().width;
final int vh = GLStateManager.getViewportState().height;
if (programChanged || vw != lastViewportWidth || vh != lastViewportHeight) {
RENDER_BACKEND.uniform2f(program.locViewportSize, vw, vh);
lastViewportWidth = vw;
lastViewportHeight = vh;
}
private void putFloat(int offset, float v) {
final long a = stagingAddress + offset;
final int bits = Float.floatToRawIntBits(v);
if (memGetInt(a) != bits) contentChanged = true;
memPutInt(a, bits);
}
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();
RENDER_BACKEND.uniform4(loc, vec4Buf);
private void putInt(int offset, int v) {
final long a = stagingAddress + offset;
if (memGetInt(a) != v) contentChanged = true;
memPutInt(a, v);
}
private void putMat4(int offset, Matrix4f m) {
putVec4(offset, m.m00(), m.m01(), m.m02(), m.m03());
putVec4(offset + 16, m.m10(), m.m11(), m.m12(), m.m13());
putVec4(offset + 32, m.m20(), m.m21(), m.m22(), m.m23());
putVec4(offset + 48, m.m30(), m.m31(), m.m32(), m.m33());
}
/** std140 mat3: three vec4-aligned columns. */
private void putMat3(int offset, Matrix3f m) {
putVec3(offset, m.m00(), m.m01(), m.m02());
putVec3(offset + 16, m.m10(), m.m11(), m.m12());
putVec3(offset + 32, m.m20(), m.m21(), m.m22());
}
private void putVec4(int offset, Vector4f v) {
putVec4(offset, v.x, v.y, v.z, v.w);
}
private void putVec4(int offset, float x, float y, float z, float w) {
putFloat(offset, x);
putFloat(offset + 4, y);
putFloat(offset + 8, z);
putFloat(offset + 12, w);
}
private void putVec3(int offset, float x, float y, float z) {
putFloat(offset, x);
putFloat(offset + 4, y);
putFloat(offset + 8, z);
}
private void putPlane(int offset, float[] plane) {
putVec4(offset, plane[0], plane[1], plane[2], plane[3]);
}
public void endFrame() {
if (ring != null) {
ring.endFrame();
}
lastFrameBlockWrites = blockWrites;
lastFrameBlockSkips = blockSkips;
lastFrameStagedMatrices = stagedMatrices;
lastFrameStagedLighting = stagedLighting;
lastFrameStagedFragment = stagedFragment;
lastFrameStagedColor = stagedColor;
lastFrameStagedNormal = stagedNormal;
lastFrameStagedTexCoord = stagedTexCoord;
lastFrameStagedLightmap = stagedLightmap;
lastFrameStagedTexGen = stagedTexGen;
lastFrameStagedClipPlanes = stagedClipPlanes;
lastFrameStagedMisc = stagedMisc;
blockWrites = 0;
blockSkips = 0;
stagedMatrices = 0;
stagedLighting = 0;
stagedFragment = 0;
stagedColor = 0;
stagedNormal = 0;
stagedTexCoord = 0;
stagedLightmap = 0;
stagedTexGen = 0;
stagedClipPlanes = 0;
stagedMisc = 0;
}
UniformRingBuffer getRing() {
return ring;
}
ByteBuffer getStaging() {
return staging;
}
public void destroy() {
memFree(mat4Buf);
memFree(mat3Buf);
memFree(vec4Buf);
memFree(vec3Buf);
memFree(staging);
memFree(clipPlaneBuf);
if (ring != null) {
ring.destroy();
ring = null;
}
bound = false;
}
}
@@ -0,0 +1,358 @@
package com.gtnewhorizons.angelica.glsm.ffp;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
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<>();
static final Int2ObjectOpenHashMap<VAOData> eboOwners = 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;
if (current.ebo != boundEBO) {
removeEboOwner(current.ebo, current);
current.ebo = boundEBO;
addEboOwner(current.ebo, current);
}
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) {
final VAOData data = vaoMap.remove(vaoId);
if (data == null) return;
removeEboOwner(data.ebo, data);
if (data == current) current = null;
}
public static void onDeleteBuffer(int buffer) {
if (buffer == 0) return;
VAOData owner = eboOwners.remove(buffer);
while (owner != null) {
final VAOData next = owner.nextEboOwner;
owner.nextEboOwner = null;
owner.ebo = 0;
owner = next;
}
}
private static void addEboOwner(int ebo, VAOData data) {
if (ebo == 0) return;
data.nextEboOwner = eboOwners.put(ebo, data);
}
private static void removeEboOwner(int ebo, VAOData data) {
if (ebo == 0) return;
VAOData node = eboOwners.get(ebo);
if (node == data) {
final VAOData next = data.nextEboOwner;
if (next == null) eboOwners.remove(ebo); else eboOwners.put(ebo, next);
data.nextEboOwner = null;
return;
}
while (node != null) {
final VAOData next = node.nextEboOwner;
if (next == data) {
node.nextEboOwner = data.nextEboOwner;
data.nextEboOwner = null;
return;
}
node = next;
}
}
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;
private VAOData nextEboOwner;
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 GLTypes.sizeBytes(type);
}
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,28 @@ 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;
private static final int BIT_LINE_STIPPLE = 42;
private static final int BIT_INSTANCED = 44;
public static final int TG_NONE = 0;
public static final int TG_OBJ_LINEAR = 1;
@@ -71,38 +80,32 @@ 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); }
public boolean lineStipple() { return bit(BIT_LINE_STIPPLE); }
public boolean instancedDraw() { return bit(BIT_INSTANCED); }
/** 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 +129,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 +174,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 +208,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,21 +219,31 @@ 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);
}
if (GLStateManager.lineStippleActive) {
bits |= (1L << BIT_LINE_STIPPLE);
}
if (GLStateManager.instancedFfpDrawActive) {
bits |= (1L << BIT_INSTANCED);
}
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,10 +264,12 @@ 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 inst=%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());
texGenModeS(), texGenModeT(), texGenModeR(), texGenModeQ(), clipPlanesEnabled(), wideLineEmulation(), instancedDraw());
}
}
@@ -29,6 +29,9 @@ public final class VertexShaderGenerator {
emitColorPassthrough(sb, key);
}
emitTexCoordPassthrough(sb, key);
if (key.lineStipple()) {
emitLineStart(sb);
}
if (key.fogEnabled()) {
emitFogDistance(sb, key);
}
@@ -42,7 +45,10 @@ 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_LineStart", "v_LineStart_gs")
.replace("v_Color", "v_Color_gs");
}
return source;
@@ -63,127 +69,45 @@ public final class VertexShaderGenerator {
if (key.hasVertexNormal()) {
sb.append("layout(location = ").append(VertexFormatElement.Usage.NORMAL.getAttributeLocation()).append(") in vec3 a_Normal;\n");
}
if (key.instancedDraw()) {
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL0).append(") in vec4 a_InstCol0;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL1).append(") in vec4 a_InstCol1;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL2).append(") in vec4 a_InstCol2;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_MATRIX_COL3).append(") in vec4 a_InstCol3;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_COLOR).append(") in vec4 a_InstColor;\n");
sb.append("layout(location = ").append(InstancedAttribs.LOC_LIGHTMAP).append(") in vec2 a_InstLightmap;\n");
}
sb.append('\n');
}
private static void emitLineStart(StringBuilder sb) {
sb.append(" vec2 ndc = gl_Position.xy / gl_Position.w;\n");
sb.append(" v_LineStart = u_Viewport.xy + (ndc * 0.5 + 0.5) * u_Viewport.zw;\n\n");
}
private static void emitUniforms(StringBuilder sb, VertexKey key) {
sb.append("// Matrices\n");
sb.append("uniform mat4 u_ModelViewMatrix;\n");
sb.append("uniform mat4 u_ProjectionMatrix;\n");
sb.append("uniform mat4 u_MVPMatrix;\n");
if (key.lightingEnabled()) {
sb.append("uniform mat3 u_NormalMatrix;\n");
}
if (key.textureMatrixEnabled()) {
sb.append("uniform mat4 u_TextureMatrix0;\n");
}
// TexGen plane uniforms
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");
if (key.texGenModeT() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneT;\n");
if (key.texGenModeR() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneR;\n");
if (key.texGenModeR() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneR;\n");
if (key.texGenModeQ() == VertexKey.TG_OBJ_LINEAR) sb.append("uniform vec4 u_TexGenObjPlaneQ;\n");
if (key.texGenModeQ() == VertexKey.TG_EYE_LINEAR) sb.append("uniform vec4 u_TexGenEyePlaneQ;\n");
if (!key.hasVertexNormal() && key.lightingEnabled()) {
sb.append("uniform vec3 u_CurrentNormal;\n");
}
if (!key.hasVertexColor()) {
sb.append("uniform vec4 u_CurrentColor;\n");
}
if (!key.hasVertexTexCoord() && key.textureEnabled()) {
sb.append("uniform vec4 u_CurrentTexCoord;\n");
}
if (key.lightmapEnabled() && !key.hasVertexLightmap()) {
sb.append("uniform vec2 u_CurrentLightmapCoord;\n");
}
if (key.lightmapEnabled()) {
sb.append("uniform mat4 u_LightmapTextureMatrix;\n");
}
if (key.lightingEnabled()) {
emitLightingUniforms(sb, key);
}
if (key.lightingEnabled() && (key.normalizeEnabled() || key.rescaleNormalsEnabled())) {
// normalize is done in shader; rescale uses normalScale
if (key.rescaleNormalsEnabled() && !key.normalizeEnabled()) {
sb.append("uniform float u_NormalScale;\n");
}
}
if (key.clipPlanesEnabled()) {
sb.append("uniform vec4 u_ClipPlane[8];\n");
}
sb.append(FFPUniformBlock.GLSL_DECL);
sb.append('\n');
}
private static void emitLightingUniforms(StringBuilder sb, VertexKey key) {
sb.append("\n// Lighting\n");
if (key.colorMaterialEnabled()) {
// When color material is active, we can't pre-compute light products so we upload raw light and material values
sb.append("uniform vec4 u_LightModelAmbient;\n");
sb.append("uniform vec4 u_MaterialEmission;\n");
sb.append("uniform vec4 u_MaterialAmbient;\n");
sb.append("uniform vec4 u_MaterialDiffuse;\n");
sb.append("uniform vec4 u_MaterialSpecular;\n");
sb.append("uniform float u_MaterialShininess;\n");
if (key.light0Enabled()) {
sb.append("uniform vec4 u_Light0Ambient;\n");
sb.append("uniform vec4 u_Light0Diffuse;\n");
sb.append("uniform vec4 u_Light0Specular;\n");
sb.append("uniform vec4 u_Light0Position;\n");
}
if (key.light1Enabled()) {
sb.append("uniform vec4 u_Light1Ambient;\n");
sb.append("uniform vec4 u_Light1Diffuse;\n");
sb.append("uniform vec4 u_Light1Specular;\n");
sb.append("uniform vec4 u_Light1Position;\n");
}
} else {
// Pre-computed: sceneColor + lightProducts
sb.append("uniform vec4 u_SceneColor;\n");
if (key.light0Enabled()) {
sb.append("uniform vec4 u_Light0Position;\n");
sb.append("uniform vec3 u_LightProd0Ambient;\n");
sb.append("uniform vec3 u_LightProd0Diffuse;\n");
sb.append("uniform vec3 u_LightProd0Specular;\n");
}
if (key.light1Enabled()) {
sb.append("uniform vec4 u_Light1Position;\n");
sb.append("uniform vec3 u_LightProd1Ambient;\n");
sb.append("uniform vec3 u_LightProd1Diffuse;\n");
sb.append("uniform vec3 u_LightProd1Specular;\n");
}
sb.append("uniform float u_MaterialShininess;\n");
}
}
private static void emitOutputs(StringBuilder sb, VertexKey key) {
sb.append("// Outputs\n");
sb.append("out vec4 v_Color;\n");
if (key.lineStipple()) {
sb.append("flat out vec2 v_LineStart;\n");
}
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");
}
@@ -193,22 +117,30 @@ public final class VertexShaderGenerator {
private static void emitPositionTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Position transform\n");
sb.append(" vec4 pos4 = vec4(a_Position, 1.0);\n");
sb.append(" gl_Position = u_MVPMatrix * pos4;\n");
if (key.instancedDraw()) {
sb.append(" mat4 instMV = mat4(a_InstCol0, a_InstCol1, a_InstCol2, a_InstCol3);\n");
sb.append(" vec4 eyePos = instMV * pos4;\n");
sb.append(" gl_Position = u_ProjectionMatrix * eyePos;\n");
} else {
sb.append(" gl_Position = u_MVPMatrix * pos4;\n");
// Eye position needed for lighting, fog, EYE_LINEAR texgen, and clip planes
if (key.lightingEnabled() || key.fogEnabled() || texGenNeedsEyePos(key) || key.clipPlanesEnabled()) {
sb.append(" vec4 eyePos = u_ModelViewMatrix * pos4;\n");
// Eye position needed for lighting, fog, EYE_LINEAR texgen, and clip planes
if (key.lightingEnabled() || key.fogEnabled() || texGenNeedsEyePos(key) || key.clipPlanesEnabled()) {
sb.append(" vec4 eyePos = u_ModelViewMatrix * pos4;\n");
}
}
sb.append('\n');
}
private static void emitNormalTransform(StringBuilder sb, VertexKey key) {
sb.append(" // Normal transform\n");
if (key.hasVertexNormal()) {
sb.append(" vec3 normal = u_NormalMatrix * a_Normal;\n");
} else {
sb.append(" vec3 normal = u_NormalMatrix * u_CurrentNormal;\n");
final String srcNormal = key.hasVertexNormal() ? "a_Normal" : "u_CurrentNormal";
if (key.instancedDraw()) {
sb.append(" vec3 normal = normalize(mat3(instMV) * ").append(srcNormal).append(");\n");
sb.append('\n');
return;
}
sb.append(" vec3 normal = u_NormalMatrix * ").append(srcNormal).append(";\n");
if (key.normalizeEnabled()) {
sb.append(" normal = normalize(normal);\n");
@@ -225,6 +157,9 @@ public final class VertexShaderGenerator {
// Color material: vertex color replaces specific material properties per glColorMaterial mode (Mesa ffvertex_prog.c get_material() bitmask approach)
sb.append(" vec4 matColor = ");
sb.append(key.hasVertexColor() ? "a_Color" : "u_CurrentColor");
if (key.instancedDraw()) {
sb.append(" * a_InstColor");
}
sb.append(";\n");
// Emission source: vertex color for CM_EMISSION, else material uniform
@@ -320,10 +255,11 @@ public final class VertexShaderGenerator {
private static void emitColorPassthrough(StringBuilder sb, VertexKey key) {
sb.append(" // Color passthrough (no lighting)\n");
if (key.hasVertexColor()) {
sb.append(" v_Color = a_Color;\n");
final String src = key.hasVertexColor() ? "a_Color" : "u_CurrentColor";
if (key.instancedDraw()) {
sb.append(" v_Color = ").append(src).append(" * a_InstColor;\n");
} else {
sb.append(" v_Color = u_CurrentColor;\n");
sb.append(" v_Color = ").append(src).append(";\n");
}
sb.append('\n');
}
@@ -331,29 +267,39 @@ 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()) {
if (key.instancedDraw()) {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_InstLightmap, 0.0, 1.0);\n");
} else if (key.hasVertexLightmap()) {
sb.append(" v_TexCoord1 = u_LightmapTextureMatrix * vec4(a_TexCoord1, 0.0, 1.0);\n");
} else {
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 +313,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,5 +8,7 @@ public final class GLSMConfig {
public static boolean hudCacheOverride;
public static volatile boolean expandVertexFormats;
private GLSMConfig() {}
}
@@ -1,12 +1,6 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.hooks.events.BlendFuncChangeEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.FogStateChangeEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.LightmapCoordsEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.ProgramChangeEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.TextureBindEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.TextureDeleteEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.TextureUnitStateEvent;
import com.gtnewhorizons.angelica.glsm.hooks.events.*;
import net.minecraftforge.eventbus.api.bus.EventBus;
public final class GLSMHooks {
@@ -14,24 +8,37 @@ public final class GLSMHooks {
public static DeferredBlendHandler blendHandler;
public static DeferredAlphaHandler alphaHandler;
public static DeferredDepthColorHandler depthColorHandler;
public static GlintColorHandler glintColorHandler;
public static ShaderWorkSubmitter shaderWorkSubmitter;
public static ShaderTransformPostProcessor postTransformProcessor;
public static PerFrameUniformBlock perFrameUniformBlock;
public static PerFrameUniformBlock perPassUniformBlock;
// EventBus instances
public static final EventBus<TextureBindEvent> TEXTURE_BIND = EventBus.create(TextureBindEvent.class);
public static final EventBus<TextureDeleteEvent> TEXTURE_DELETE = EventBus.create(TextureDeleteEvent.class);
public static final EventBus<TextureUnitStateEvent> TEXTURE_UNIT_STATE = EventBus.create(TextureUnitStateEvent.class);
public static final EventBus<ProgramChangeEvent> PROGRAM_CHANGE = EventBus.create(ProgramChangeEvent.class);
public static final EventBus<ProgramDeleteEvent> PROGRAM_DELETE = EventBus.create(ProgramDeleteEvent.class);
public static final EventBus<BlendFuncChangeEvent> BLEND_FUNC_CHANGE = EventBus.create(BlendFuncChangeEvent.class);
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);
public static final EventBus<LoadingCheckpointEvent> LOADING_CHECKPOINT = EventBus.create(LoadingCheckpointEvent.class);
// Reusable event instances
public static final TextureBindEvent textureBindEvent = new TextureBindEvent();
public static final TextureDeleteEvent textureDeleteEvent = new TextureDeleteEvent();
public static final TextureUnitStateEvent textureUnitStateEvent = new TextureUnitStateEvent();
public static final ProgramChangeEvent programChangeEvent = new ProgramChangeEvent();
public static final ProgramDeleteEvent programDeleteEvent = new ProgramDeleteEvent();
public static final BlendFuncChangeEvent blendFuncChangeEvent = new BlendFuncChangeEvent();
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();
public static final LoadingCheckpointEvent loadingCheckpointEvent = new LoadingCheckpointEvent();
private GLSMHooks() {}
}
@@ -2,56 +2,47 @@ package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizon.gtnhlib.client.renderer.DirectTessellator;
import com.gtnewhorizons.angelica.glsm.streaming.StreamingUploader;
import lombok.Getter;
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;
@Getter private final boolean lwjglDebug;
@Getter private final StreamingUploader.UploadStrategy streamingUploadStrategy;
@Getter private final Consumer<DirectTessellator> directDrawer;
@Getter private final Runnable streamingDrawerDestroy;
@Getter private final int displayWidth;
@Getter private final int displayHeight;
@Getter private final Runnable postInitCallback;
@Getter private final boolean DSAEnabled;
@Getter 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.DSAEnabled = builder.enableDSA;
}
public static Builder builder() {
return new Builder();
}
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 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 +56,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 +82,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);
}
@@ -0,0 +1,18 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.shader.UniformType;
import java.util.List;
/**
* Shared per-frame uniform block for SDL GPU. Produced by Iris once per shader pack load. Every shader declares the
* whole block regardless of which members it reads.
*/
public record PerFrameUniformBlock(List<Member> members) {
public record Member(String name, UniformType type) {}
public boolean isEmpty() {
return members.isEmpty();
}
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import com.gtnewhorizons.angelica.glsm.shader.ShaderType;
@FunctionalInterface
public interface ShaderTransformPostProcessor {
void onTransformed(String transformedSource, ShaderType shaderType);
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.glsm.hooks;
import java.util.concurrent.CompletableFuture;
import java.util.function.Supplier;
public interface ShaderWorkSubmitter {
<T> CompletableFuture<T> submit(Supplier<T> work);
}
@@ -0,0 +1,9 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
/**
* AlphaTest changed - Listeners should query GLSM alpha state directly if they care what changed.
*/
public final class AlphaStateChangeEvent extends MutableEvent {
}
@@ -0,0 +1,8 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
/** Fired at loading-phase boundaries */
public final class LoadingCheckpointEvent extends MutableEvent {
public boolean requiresSync;
}
@@ -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,7 @@
package com.gtnewhorizons.angelica.glsm.hooks.events;
import net.minecraftforge.eventbus.api.event.MutableEvent;
public final class ProgramDeleteEvent extends MutableEvent {
public int program;
}
@@ -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;
}
@@ -0,0 +1,32 @@
package com.gtnewhorizons.angelica.glsm.loading;
import java.util.List;
/**
* Verifies required classes/resources are present on the classpath at load time.
* Fails fast with a descriptive error message if a dependency is missing.
*/
public final class DependencyVerifier {
/**
* A single dependency check: a classpath resource that must exist.
* @param resourcePath Resource path to check (e.g. "/com/example/SomeClass.class")
* @param errorMessage Human-readable error message if missing (include download URL)
*/
public record Check(String resourcePath, String errorMessage) {}
/**
* Verify all checks pass. Throws RuntimeException on first failure.
* @param anchor Class whose classloader to use for resource lookups
* @param checks Dependency checks to run
*/
public static void verify(Class<?> anchor, List<Check> checks) {
for (Check check : checks) {
if (anchor.getResource(check.resourcePath()) == null) {
throw new RuntimeException(check.errorMessage());
}
}
}
private DependencyVerifier() {}
}
@@ -0,0 +1,54 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.angelica.glsm.redirect.GLSMRedirector;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.tree.ClassNode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public final class EarlyRedirectorCore {
private final GLSMRedirector core;
private final String[] targets;
private final String[] exclusions;
public EarlyRedirectorCore(String... extraExclusions) {
this.core = new GLSMRedirector();
this.targets = EcosystemNarrowRules.EARLY_REDIRECTOR_TARGETS.clone();
final List<String> excl = new ArrayList<>(Arrays.asList(core.getCoreExclusions()));
if (extraExclusions != null && extraExclusions.length > 0) {
Collections.addAll(excl, extraExclusions);
}
this.exclusions = excl.toArray(new String[0]);
}
public byte[] transform(String transformedName, byte[] basicClass) {
if (basicClass == null) return null;
if (!startsWithAny(transformedName, targets)) return basicClass;
if (startsWithAny(transformedName, exclusions)) return basicClass;
if (!core.shouldTransform(basicClass)) return basicClass;
final ClassReader cr = new ClassReader(basicClass);
final ClassNode cn = new ClassNode();
cr.accept(cn, 0);
if (core.transformClassNode(transformedName, cn)) {
final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
cn.accept(cw);
return cw.toByteArray();
}
return basicClass;
}
private static boolean startsWithAny(String name, String[] prefixes) {
for (String p : prefixes) {
if (name.startsWith(p)) return true;
}
return false;
}
}
@@ -0,0 +1,104 @@
package com.gtnewhorizons.angelica.glsm.loading;
import com.gtnewhorizons.angelica.glsm.loading.TransformerNarrower.NarrowRule;
import java.util.List;
public final class EcosystemNarrowRules {
public static final List<NarrowRule> ALL = List.of(
new NarrowRule("DragonAPI", "Reika.DragonAPI.ASM", List.of(
"Reika.DragonAPI.ASM.Patchers",
"Reika.DragonAPI.ASM.Profiling",
"Reika.DragonAPI.ASM.APIStripper",
"Reika.DragonAPI.ASM.ClassReparenter",
"Reika.DragonAPI.ASM.DependentMethodStripper",
"Reika.DragonAPI.ASM.DragonAPIClassTransformer",
"Reika.DragonAPI.ASM.FMLItemBlockPatch",
"Reika.DragonAPI.ASM.FluidNamePatch",
"Reika.DragonAPI.ASM.InterfaceInjector",
"Reika.DragonAPI.ASM.SpecialBiomePlacement",
"Reika.DragonAPI.ASM.StructureLootHooks"
)),
new NarrowRule("Xaeros", "xaero.common.core", List.of(
"xaero.common.core.transformer"
)),
new NarrowRule("Xaeros", "xaero.map.core", List.of(
"xaero.map.core.transformer"
)),
new NarrowRule("AdvancedLightsabers", "com.fiskmods.lightsabers.asm", List.of(
"com.fiskmods.lightsabers.asm.ALLoadingPlugin",
"com.fiskmods.lightsabers.asm.ASMHooks",
"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",
"alfheim.common.core.asm.hook.Botania18AndUpBackport",
"alfheim.common.core.asm.hook.ElementalDamageAdapter",
"alfheim.common.core.asm.hook.integration",
"alfheim.common.core.asm.hook.extender.FurnaceExtender",
"alfheim.common.core.asm.hook.extender.ItemAuraRingExtender",
"alfheim.common.core.asm.hook.extender.ItemLensExtender",
"alfheim.common.core.asm.hook.extender.ItemTwigWandExtender",
"alfheim.common.core.asm.hook.extender.LightRelayExtender",
"alfheim.common.core.asm.hook.extender.PureDaisyExtender",
"alfheim.common.core.asm.hook.extender.QuartzExtender",
"alfheim.common.core.asm.hook.extender.RelicHooks",
"alfheim.common.core.asm.hook.extender.SparkExtender",
"alfheim.common.core.asm.hook.fixes.BotaniaGlowingRenderFixes",
"alfheim.common.core.asm.hook.fixes.CorporeaInputFix",
"alfheim.common.core.asm.hook.fixes.GodAttributesHooks",
"alfheim.common.core.asm.hook.fixes.RecipeAncientWillsFix"
)),
new NarrowRule("LegendsMod", "com.tihyo.legends.management.asm", List.of(
"com.tihyo.legends.management.asm.LegendsLoadingPlugin",
"com.tihyo.legends.management.asm.AbstractTransformer",
"com.tihyo.legends.management.asm.ASMHelper",
"com.tihyo.legends.management.asm.Transformer"
))
);
public static final String[] LWJGL3IFY_EXCLUSIONS_SHARED = {
"org.embeddedt.embeddium",
"org.taumc.celeritas",
"com.mitchej123.lwjgl.lwjgl3",
"com.mitchej123.glsm",
"com.gtnewhorizons.angelica.lwjgl3",
};
public static final String[] EARLY_REDIRECTOR_TARGETS = {
"cn.tesseract.mycelium.",
};
private EcosystemNarrowRules() {}
}
@@ -0,0 +1,66 @@
package com.gtnewhorizons.angelica.glsm.loading;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Narrows overly-broad transformer exclusions that prevent the GLSM redirector from
* transforming GL calls in mod code. Some coremods register exclusions covering their
* entire package tree, which inadvertently shields rendering code from redirection.
* <p>
* This utility removes the broad exclusion and re-adds narrower ones that cover only
* the coremod's own transformer/ASM classes, leaving rendering code exposed.
* <p>
* Operates on a {@code Set<String>} (the transformer exceptions set from LaunchClassLoader)
* and a {@code Map<String, Object>} (the Launch blackboard). Callers extract these from
* the Forge/launchwrapper infrastructure.
*/
public final class TransformerNarrower {
private static final Logger LOGGER = LogManager.getLogger("GLSM");
/**
* A rule that replaces a broad transformer exclusion with narrower ones.
* @param name Blackboard/config key suffix. (e.g. "DragonAPI").
* @param broad The broad exclusion to remove (e.g. "Reika.DragonAPI.ASM")
* @param narrow Narrower exclusions to add in its place
*/
public record NarrowRule(String name, String broad, List<String> narrow) {}
/**
* Apply narrowing rules to the transformer exceptions set.
*
* @param exceptions The mutable Set from LaunchClassLoader.transformerExceptions
* @param blackboard The Launch.blackboard map (checked for disable flags)
* @param keyPrefix Blackboard key prefix for per-rule opt-out (e.g. "angelica" or "umbra").
* Rule "DragonAPI" checks key "{prefix}.narrow.DragonAPI". If explicitly
* set to Boolean.FALSE, the rule is skipped.
* @param callerName Name for log messages (e.g. "Angelica" or "Umbra")
* @param rules The narrowing rules to apply
* @return number of rules that were applied
*/
public static int narrow(Set<String> exceptions, Map<String, Object> blackboard,
String keyPrefix, String callerName, List<NarrowRule> rules) {
int applied = 0;
for (NarrowRule rule : rules) {
final String key = keyPrefix + ".narrow." + rule.name();
if (Boolean.FALSE.equals(blackboard.get(key))) {
continue;
}
if (exceptions.remove(rule.broad())) {
for (String narrow : rule.narrow()) {
exceptions.add(narrow);
}
LOGGER.info("[{}] Narrowed {} transformer exclusion to allow GL redirection", callerName, rule.broad());
applied++;
}
}
return applied;
}
private TransformerNarrower() {}
}
@@ -0,0 +1,422 @@
package com.gtnewhorizons.angelica.glsm.profiling;
import com.gtnewhorizons.angelica.config.SystemProperties;
import com.gtnewhorizons.angelica.glsm.CaptureGate;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.sun.management.ThreadMXBean;
import lombok.Setter;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@SuppressWarnings("ForLoopReplaceableByForEach")
public final class Tracy {
private static final Logger LOGGER = LogManager.getLogger("Tracy");
public static final boolean ENABLED;
public static final boolean FINE_ZONES;
private static final TracyBackend BACKEND;
private static volatile boolean SHUTTING_DOWN;
private static final ConcurrentHashMap<String, Long> CLIENT_SRC_LOCS = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, Long> SERVER_SRC_LOCS = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, Long> PLAIN_SRC_LOCS = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, Long> PLOT_NAMES = new ConcurrentHashMap<>();
private static final ConcurrentHashMap<String, Long> FRAME_NAMES = new ConcurrentHashMap<>();
private static final Set<String> WARNED_LEAKS = ConcurrentHashMap.newKeySet();
public static final int COLOR_CLIENT = 0x3A6EA5; // blue
public static final int COLOR_SERVER = 0xB5651D; // brown
public static final int COLOR_WORKER = 0x3F7F3F; // green
public static final int COLOR_SWAP = 0xAA3333; // red
public static final int COLOR_TERRAIN = 0x2E8B8B; // teal
public static final int COLOR_IRIS = 0x9E862E; // gold
public static final int COLOR_FFP = 0x7A4E9E; // purple
private static final ThreadLocal<ZoneStack> STACK = new ThreadLocal<>() {
@Override
protected ZoneStack initialValue() {
BACKEND.setCurrentThreadName(Thread.currentThread().getName());
return new ZoneStack();
}
};
static {
TracyBackend backend = null;
if (SystemProperties.TRACY) {
backend = loadAndInit();
}
BACKEND = backend;
ENABLED = backend != null;
FINE_ZONES = ENABLED && SystemProperties.TRACY_FINE_ZONES;
if (ENABLED) {
Runtime.getRuntime().addShutdownHook(new Thread(Tracy::shutdown, "Tracy-Shutdown"));
LOGGER.info("Tracy profiling enabled");
}
}
private Tracy() {}
private static void shutdown() {
SHUTTING_DOWN = true;
final Thread worker = new Thread(BACKEND::shutdown, "Tracy-Shutdown-Worker");
worker.setDaemon(true);
worker.start();
try {
worker.join(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
private static TracyBackend loadAndInit() {
TracyBackend found = null;
try {
final Iterator<TracyBackend> it = ServiceLoader.load(TracyBackend.class, TracyBackend.class.getClassLoader()).iterator();
while (found == null && it.hasNext()) {
found = it.next();
}
} catch (ServiceConfigurationError | LinkageError e) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but backend unavailable: {}", e.getMessage());
return null;
}
if (found == null) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but no TracyBackend service present");
return null;
}
final TracyBackend backend = found;
final CountDownLatch done = new CountDownLatch(1);
final AtomicReference<Boolean> result = new AtomicReference<>();
final Thread init = new Thread(new InitRunner(backend, done, result), "Tracy-Init");
init.setDaemon(true);
init.start();
try {
if (!done.await(15, TimeUnit.SECONDS) && result.compareAndSet(null, Boolean.FALSE)) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but init timed out; profiling disabled");
return null;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
if (!result.compareAndSet(null, Boolean.FALSE) && Boolean.TRUE.equals(result.get())) {
backend.shutdown();
}
return null;
}
if (!Boolean.TRUE.equals(result.get())) {
LOGGER.warn("Tracy requested (-Dangelica.tracy=true) but native init failed; profiling disabled");
return null;
}
return backend;
}
private record InitRunner(TracyBackend backend, CountDownLatch done, AtomicReference<Boolean> result) implements Runnable {
@Override
public void run() {
final boolean ok = backend.init();
if (!result.compareAndSet(null, ok) && ok) {
backend.shutdown();
}
done.countDown();
}
}
public static void beginSection(String name) {
if (!ENABLED) return;
if ("root".equals(name)) {
unwindToRoot(STACK.get());
}
if (!CaptureGate.markersThisFrame) {
skip(name);
return;
}
final boolean client = Thread.currentThread() == GLStateManager.getMainThread();
final ConcurrentHashMap<String, Long> map = client ? CLIENT_SRC_LOCS : SERVER_SRC_LOCS;
Long srcLoc = map.get(name);
if (srcLoc == null) {
srcLoc = map.computeIfAbsent(name, n -> BACKEND.internSrcLoc((client ? "cl:" : "sv:") + n, client ? classifySection(n) : COLOR_SERVER));
}
begin(srcLoc, name);
}
private static int classifySection(String name) {
if (name.startsWith("iris_")) return COLOR_IRIS;
if (name.startsWith("draw_chunk_layer")) return COLOR_TERRAIN;
return COLOR_CLIENT;
}
private static void unwindToRoot(ZoneStack stack) {
while (stack.depth() > 0 || stack.hasSkipped()) {
final long ctx = stack.pop();
if (ctx == ZoneStack.EMPTY) continue;
if (stack.poppedGpu()) {
BACKEND.gpuEndZone();
}
if (ctx != 0L) {
BACKEND.endZone(ctx);
}
final String name = stack.poppedName();
if (name != null && WARNED_LEAKS.add(name)) {
LOGGER.warn("Tracy: unbalanced profiler section '{}' left open (leaked by its caller); closed at next root", name);
BACKEND.message("unbalanced profiler section '" + name + "' closed at root");
}
}
}
public static void beginZone(String name) {
beginZone(name, COLOR_WORKER);
}
public static void beginZone(String name, int color) {
if (!ENABLED || SHUTTING_DOWN) return;
if (!CaptureGate.markersThisFrame) {
skip(name);
return;
}
Long srcLoc = PLAIN_SRC_LOCS.get(name);
if (srcLoc == null) {
srcLoc = PLAIN_SRC_LOCS.computeIfAbsent(name, n -> BACKEND.internSrcLoc(n, color));
}
begin(srcLoc, name);
}
public static final class ZoneId {
private final long srcLoc;
private final String name;
private ZoneId(long srcLoc, String name) {
this.srcLoc = srcLoc;
this.name = name;
}
}
public static ZoneId zoneId(String name) {
return zoneId(name, COLOR_WORKER);
}
public static ZoneId zoneId(String name, int color) {
if (!ENABLED) return new ZoneId(0L, name);
Long srcLoc = PLAIN_SRC_LOCS.get(name);
if (srcLoc == null) {
srcLoc = PLAIN_SRC_LOCS.computeIfAbsent(name, n -> BACKEND.internSrcLoc(n, color));
}
return new ZoneId(srcLoc, name);
}
public static void beginZone(ZoneId zone) {
if (!ENABLED || SHUTTING_DOWN) return;
begin(zone.srcLoc, zone.name);
}
private static void skip(String name) {
STACK.get().push(0L, false, name);
}
private static void begin(long srcLoc, String name) {
if (SHUTTING_DOWN) return;
final ZoneStack stack = STACK.get();
if (srcLoc == 0 || !CaptureGate.markersThisFrame || stack.atCap()) {
stack.push(0L, false, name);
return;
}
final long ctx = BACKEND.beginZone(srcLoc);
final boolean gpu = gpuReady && gpuZonesEnabled && Thread.currentThread() == GLStateManager.getMainThread() && BACKEND.gpuBeginZone(srcLoc);
stack.push(ctx, gpu, name);
if (srcLoc == BACKEND.dynamicSrcLoc()) {
BACKEND.zoneText(ctx, name);
}
}
public static void endZone() {
if (!ENABLED || SHUTTING_DOWN) return;
final ZoneStack stack = STACK.get();
final long ctx = stack.pop();
if (ctx == ZoneStack.EMPTY) return;
if (stack.poppedGpu()) {
BACKEND.gpuEndZone();
}
if (ctx != 0L) {
BACKEND.endZone(ctx);
}
}
public static void zoneText(String text) {
if (!ENABLED || SHUTTING_DOWN) return;
final long ctx = STACK.get().peek();
if (ctx != ZoneStack.EMPTY && ctx != 0L) {
BACKEND.zoneText(ctx, text);
}
}
public static void zoneValue(long value) {
if (!ENABLED || SHUTTING_DOWN) return;
final long ctx = STACK.get().peek();
if (ctx != ZoneStack.EMPTY && ctx != 0L) {
BACKEND.zoneValue(ctx, value);
}
}
private static boolean threadNameRefreshed;
public static void frameMark() {
if (!ENABLED || SHUTTING_DOWN) return;
if (!threadNameRefreshed) {
threadNameRefreshed = true;
BACKEND.setCurrentThreadName(Thread.currentThread().getName());
}
BACKEND.frameMark();
}
public static void frameMark(String name) {
if (!ENABLED || SHUTTING_DOWN) return;
Long ptr = FRAME_NAMES.get(name);
if (ptr == null) {
ptr = FRAME_NAMES.computeIfAbsent(name, BACKEND::internFrameName);
}
if (ptr != 0) BACKEND.frameMark(ptr);
}
public static void plotInt(String name, long value) {
plotInt(name, value, TracyBackend.PLOT_FORMAT_NUMBER);
}
public static void plotInt(String name, long value, int format) {
if (!ENABLED || SHUTTING_DOWN || !CaptureGate.markersThisFrame) return;
final long ptr = plotName(name, format);
if (ptr != 0) BACKEND.plotInt(ptr, value);
}
public static void plot(String name, double value) {
if (!ENABLED || SHUTTING_DOWN || !CaptureGate.markersThisFrame) return;
final long ptr = plotName(name, TracyBackend.PLOT_FORMAT_NUMBER);
if (ptr != 0) BACKEND.plot(ptr, value);
}
private static long plotName(String name, int format) {
Long ptr = PLOT_NAMES.get(name);
if (ptr == null) {
ptr = PLOT_NAMES.computeIfAbsent(name, n -> BACKEND.internPlotName(n, format));
}
return ptr;
}
public static long plotHandle(String name, int format) {
if (!ENABLED || SHUTTING_DOWN) return 0;
return plotName(name, format);
}
public static long plotHandle(String name) {
return plotHandle(name, TracyBackend.PLOT_FORMAT_NUMBER);
}
public static void plotInt(long handle, long value) {
if (handle == 0 || SHUTTING_DOWN || !CaptureGate.markersThisFrame) return;
BACKEND.plotInt(handle, value);
}
public static void plot(long handle, double value) {
if (handle == 0 || SHUTTING_DOWN || !CaptureGate.markersThisFrame) return;
BACKEND.plot(handle, value);
}
public static void message(String text) {
if (!ENABLED || SHUTTING_DOWN || !CaptureGate.markersThisFrame) return;
BACKEND.message(text);
}
public static boolean isConnected() {
return ENABLED && BACKEND.isConnected();
}
private static boolean gpuInitAttempted;
private static boolean gpuReady;
@Setter private static boolean gpuZonesEnabled = true;
public static void gpuCollect() {
if (!ENABLED) return;
if (!gpuInitAttempted) {
gpuInitAttempted = true;
gpuReady = BACKEND.gpuInit();
}
if (gpuReady) BACKEND.gpuCollect();
}
private static final class AllocBean {
static final ThreadMXBean BEAN;
static {
ThreadMXBean bean = null;
try {
if (ManagementFactory.getThreadMXBean() instanceof ThreadMXBean sun && sun.isThreadAllocatedMemorySupported()) {
bean = sun;
}
} catch (Throwable ignored) {}
BEAN = bean;
}
}
private static final ThreadLocal<long[]> PREV_ALLOC = ThreadLocal.withInitial(() -> new long[1]);
public static void plotAllocRate(long plotHandle) {
if (!ENABLED || SHUTTING_DOWN || AllocBean.BEAN == null) return;
if (!CaptureGate.markersThisFrame) {
PREV_ALLOC.get()[0] = 0;
return;
}
final long now = AllocBean.BEAN.getThreadAllocatedBytes(Thread.currentThread().threadId());
if (now < 0) return;
final long[] prev = PREV_ALLOC.get();
if (prev[0] != 0) {
plotInt(plotHandle, now - prev[0]);
}
prev[0] = now;
}
private static List<GarbageCollectorMXBean> gcBeans;
private static boolean gcBaselineValid;
private static long prevGcCount;
private static long prevGcTimeMs;
private static long gcCountHandle;
private static long gcTimeHandle;
public static void plotGcStats() {
if (!ENABLED || SHUTTING_DOWN) return;
if (!CaptureGate.markersThisFrame) {
gcBaselineValid = false;
return;
}
if (gcBeans == null) {
gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
gcCountHandle = plotHandle("gcCount");
gcTimeHandle = plotHandle("gcTimeMs");
}
long count = 0;
long timeMs = 0;
for (int i = 0; i < gcBeans.size(); i++) {
final GarbageCollectorMXBean bean = gcBeans.get(i);
final long c = bean.getCollectionCount();
final long t = bean.getCollectionTime();
if (c > 0) count += c;
if (t > 0) timeMs += t;
}
if (gcBaselineValid) {
plotInt(gcCountHandle, count - prevGcCount);
plotInt(gcTimeHandle, timeMs - prevGcTimeMs);
if (count > prevGcCount) {
message("GC: +" + (count - prevGcCount) + " collections, +" + (timeMs - prevGcTimeMs) + " ms");
}
}
gcBaselineValid = true;
prevGcCount = count;
prevGcTimeMs = timeMs;
}
}
@@ -0,0 +1,36 @@
package com.gtnewhorizons.angelica.glsm.profiling;
public interface TracyBackend {
int PLOT_FORMAT_NUMBER = 0;
int PLOT_FORMAT_MEMORY = 1;
int PLOT_FORMAT_PERCENTAGE = 2;
boolean init();
long internSrcLoc(String name, int color);
long dynamicSrcLoc();
long beginZone(long srcLoc);
void endZone(long ctx);
void zoneText(long ctx, String text);
void zoneValue(long ctx, long value);
void frameMark();
void frameMark(long namePtr);
long internFrameName(String name);
long internPlotName(String name, int format);
void plotInt(long namePtr, long value);
void plot(long namePtr, double value);
void message(String text);
void setCurrentThreadName(String name);
boolean isConnected();
void shutdown();
boolean gpuInit();
boolean gpuBeginZone(long srcLoc);
void gpuEndZone();
void gpuCollect();
}
@@ -0,0 +1,87 @@
package com.gtnewhorizons.angelica.glsm.profiling;
public final class ZoneStack {
public static final long EMPTY = -1L;
static final int MAX_DEPTH = 512;
private long[] stack = new long[64];
private boolean[] gpu = new boolean[64];
private String[] names = new String[64];
private int depth;
private int skipped;
private boolean lastPoppedGpu;
private String lastPoppedName;
public boolean atCap() {
return depth == MAX_DEPTH;
}
public boolean push(long ctx) {
return push(ctx, false, null);
}
public boolean push(long ctx, boolean gpuBracketed) {
return push(ctx, gpuBracketed, null);
}
public boolean push(long ctx, boolean gpuBracketed, String name) {
if (depth == MAX_DEPTH) {
skipped++;
return false;
}
if (depth == stack.length) {
final long[] grownStack = new long[stack.length * 2];
System.arraycopy(stack, 0, grownStack, 0, stack.length);
stack = grownStack;
final boolean[] grownGpu = new boolean[gpu.length * 2];
System.arraycopy(gpu, 0, grownGpu, 0, gpu.length);
gpu = grownGpu;
final String[] grownNames = new String[names.length * 2];
System.arraycopy(names, 0, grownNames, 0, names.length);
names = grownNames;
}
gpu[depth] = gpuBracketed;
names[depth] = name;
stack[depth++] = ctx;
return true;
}
public long pop() {
if (skipped > 0) {
skipped--;
lastPoppedGpu = false;
lastPoppedName = null;
return EMPTY;
}
if (depth == 0) {
lastPoppedGpu = false;
lastPoppedName = null;
return EMPTY;
}
lastPoppedGpu = gpu[--depth];
lastPoppedName = names[depth];
names[depth] = null;
return stack[depth];
}
public boolean poppedGpu() {
return lastPoppedGpu;
}
public String poppedName() {
return lastPoppedName;
}
public boolean hasSkipped() {
return skipped > 0;
}
public long peek() {
if (skipped > 0 || depth == 0) return EMPTY;
return stack[depth - 1];
}
public int depth() {
return depth;
}
}
@@ -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);
}
@@ -0,0 +1,198 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
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;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public final class AttribSnapshot {
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 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 * GLTypes.sizeBytes(type);
}
public int typeSizeBytes() {
return GLTypes.sizeBytes(type);
}
}
private final AttribDesc[] perLocation;
private final List<ByteBuffer> allocatedBuffers;
private boolean freed;
private AttribSnapshot(AttribDesc[] perLocation, List<ByteBuffer> allocatedBuffers) {
this.perLocation = perLocation;
this.allocatedBuffers = allocatedBuffers;
}
public AttribDesc get(int location) {
return (location >= 0 && location < perLocation.length) ? perLocation[location] : null;
}
public int maxLocations() {
return perLocation.length;
}
public void free() {
if (freed) return;
freed = true;
for (ByteBuffer buf : allocatedBuffers) {
MemoryUtilities.memFree(buf);
}
allocatedBuffers.clear();
}
/**
* Snapshot attribs over a tight vertex window {@code [firstVertex, firstVertex+vertexCount)}.
*
* <p>Hot path (single VBO backing every enabled attrib — the typical draw) runs without
* 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[VAOManager.MAX_ATTRIBS];
final List<ByteBuffer> allocated = new ArrayList<>();
final int prevVBO = GLStateManager.getBoundVBO();
boolean success = false;
// Pass 1 — unioned [start,end] per vboId. Hot path: one source VBO, zero allocations.
int rangeSoleVboId = 0;
long rangeSoleStart = Long.MAX_VALUE;
long rangeSoleEnd = Long.MIN_VALUE;
Int2ObjectOpenHashMap<long[]> vboRanges = null;
final long lastVtx = (long) firstVertex + vertexCount - 1;
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 * GLTypes.sizeBytes(a.type);
final long start = a.offset + (long) firstVertex * stride;
final long end = a.offset + lastVtx * stride + typeBytes;
if (vboRanges != null) {
long[] r = vboRanges.get(a.vboId);
if (r == null) {
vboRanges.put(a.vboId, new long[] { start, end });
} else {
if (start < r[0]) r[0] = start;
if (end > r[1]) r[1] = end;
}
} else if (rangeSoleVboId == 0 || rangeSoleVboId == a.vboId) {
rangeSoleVboId = a.vboId;
if (start < rangeSoleStart) rangeSoleStart = start;
if (end > rangeSoleEnd) rangeSoleEnd = end;
} else {
// Spill: second distinct vboId. Promote the accumulated range to the map.
vboRanges = new Int2ObjectOpenHashMap<>();
vboRanges.put(rangeSoleVboId, new long[] { rangeSoleStart, rangeSoleEnd });
vboRanges.put(a.vboId, new long[] { start, end });
}
}
// Pass 2 — readback cache. Same spill pattern.
int readSoleVboId = 0;
ByteBuffer readSoleBuf = null;
long readSoleOffset = 0;
Int2ObjectOpenHashMap<ByteBuffer> vboBuffers = null;
Int2LongOpenHashMap vboReadOffsets = null;
try {
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;
long readOffset;
if (vboBuffers != null) {
buf = vboBuffers.get(a.vboId);
readOffset = buf != null ? vboReadOffsets.get(a.vboId) : 0;
} else if (readSoleVboId == a.vboId) {
buf = readSoleBuf;
readOffset = readSoleOffset;
} else {
buf = null;
readOffset = 0;
}
if (buf == null) {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, a.vboId);
final int bufferSize = GLStateManager.glGetBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (bufferSize <= 0) {
warnOnce("vbo-orphaned", "[AttribSnapshot] GL_BUFFER_SIZE={} for VBO {} -- skipping capture", bufferSize, a.vboId);
return null;
}
final long rStart;
final long rEnd;
if (vboRanges != null) {
final long[] range = vboRanges.get(a.vboId);
rStart = range[0];
rEnd = range[1];
} else {
rStart = rangeSoleStart;
rEnd = rangeSoleEnd;
}
if (rEnd > bufferSize) {
warnOnce("vbo-overrun", "[AttribSnapshot] attrib range {}..{} exceeds VBO {} size {} -- skipping capture", rStart, rEnd, a.vboId, bufferSize);
return null;
}
final long ro = Math.max(0, rStart);
final int rs = (int) (rEnd - ro);
if (rs <= 0) {
warnOnce("vbo-empty-range", "[AttribSnapshot] empty attrib range {}..{} for VBO {} -- skipping capture", rStart, rEnd, a.vboId);
return null;
}
buf = MemoryUtilities.memAlloc(rs);
GLStateManager.glGetBufferSubData(GL15.GL_ARRAY_BUFFER, ro, buf);
allocated.add(buf);
readOffset = ro;
if (vboBuffers != null) {
vboBuffers.put(a.vboId, buf);
vboReadOffsets.put(a.vboId, ro);
} else if (readSoleVboId == 0) {
readSoleVboId = a.vboId;
readSoleBuf = buf;
readSoleOffset = ro;
} else {
vboBuffers = new Int2ObjectOpenHashMap<>();
vboReadOffsets = new Int2LongOpenHashMap();
vboBuffers.put(readSoleVboId, readSoleBuf);
vboReadOffsets.put(readSoleVboId, readSoleOffset);
vboBuffers.put(a.vboId, buf);
vboReadOffsets.put(a.vboId, ro);
}
}
out[i] = new AttribDesc(i, a.size, a.type, a.normalized, a.stride, a.offset, a.vboId, buf, readOffset);
} else if (a.clientPointer != null) {
out[i] = new AttribDesc(i, a.size, a.type, a.normalized, a.stride, a.clientPointer.position(), 0, a.clientPointer, 0L);
}
}
success = true;
return new AttribSnapshot(out, allocated);
} finally {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
if (!success) {
for (ByteBuffer b : allocated) MemoryUtilities.memFree(b);
}
}
}
}
@@ -1,784 +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 writeDrawArrays(int mode, int start, int count) {
ensureCapacity(16);
writeInt(GLCommand.DRAW_ARRAYS);
writeInt(mode);
writeInt(start);
writeInt(count);
}
public void writeDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
ensureCapacity(24);
writeInt(GLCommand.DRAW_ELEMENTS);
writeInt(mode);
writeInt(indices_count);
writeInt(type);
writeLong(indices_buffer_offset);
}
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++) {
@@ -530,14 +507,6 @@ public final class CommandBufferExecutor {
ptr += 4 * MAX_DRAW_BUFFERS;
GLStateManager.glDrawBuffers(DRAW_BUFFERS_BUFFER);
}
case GLCommand.DRAW_ARRAYS -> {
GLStateManager.glDrawArrays(memGetInt(ptr), memGetInt(ptr + 4), memGetInt(ptr + 8));
ptr += 12;
}
case GLCommand.DRAW_ELEMENTS -> {
GLStateManager.glDrawElements(memGetInt(ptr), memGetInt(ptr + 4), memGetInt(ptr + 8), memGetLong(ptr + 12));
ptr += 20;
}
case GLCommand.BIND_VBO -> {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, memGetInt(ptr));
ptr += 4;
@@ -551,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,168 +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.DRAW_ARRAYS -> out.writeDrawArrays(raw.readInt(), raw.readInt(), raw.readInt());
case GLCommand.DRAW_ELEMENTS -> out.writeDrawElements(raw.readInt(), raw.readInt(), raw.readInt(), raw.readLong());
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);
}
}
}
@@ -1,374 +1,701 @@
package com.gtnewhorizons.angelica.glsm.recording;
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 int commandCount;
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();
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 writeIndexedDrawCapture(IndexedDrawCapture capture) {
indexedDraws.add(capture);
writeComplexCommand(capture.placeholder);
}
public void free() {
buffer.free();
for (IndexedDrawCapture c : indexedDraws.getCaptures()) {
c.freeBuffers();
}
}
// ==================== 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++;
}
public void recordLoadMatrix(Matrix4f matrix) {
buffer.writeLoadMatrix(matrix);
commandCount++;
}
// === Two int commands ===
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 writeBindTexture(int target, int texture) {
ensureCapacity(12);
writeInt(GLCommand.BIND_TEXTURE);
writeInt(target);
writeInt(texture);
}
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 writePolygonMode(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_MODE);
writeInt(face);
writeInt(mode);
}
public void recordViewport(int x, int y, int width, int height) {
buffer.writeViewport(x, y, width, height);
commandCount++;
public void writeColorMaterial(int face, int mode) {
ensureCapacity(12);
writeInt(GLCommand.COLOR_MATERIAL);
writeInt(face);
writeInt(mode);
}
public void recordScissor(int x, int y, int width, int height) {
buffer.writeScissor(x, y, width, height);
commandCount++;
public void writeLineStipple(int factor, int pattern) {
ensureCapacity(12);
writeInt(GLCommand.LINE_STIPPLE);
writeInt(factor);
writeInt(pattern);
}
public void recordPointSize(float size) {
buffer.writePointSize(size);
commandCount++;
public void writeStencilMaskSeparate(int face, int mask) {
ensureCapacity(12);
writeInt(GLCommand.STENCIL_MASK_SEPARATE);
writeInt(face);
writeInt(mask);
}
public void recordLineWidth(float width) {
buffer.writeLineWidth(width);
commandCount++;
public void writeFogi(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.FOGI);
writeInt(pname);
writeInt(param);
}
public void recordLineStipple(int factor, int pattern) {
buffer.writeLineStipple(factor, pattern);
commandCount++;
public void writeHint(int target, int mode) {
ensureCapacity(12);
writeInt(GLCommand.HINT);
writeInt(target);
writeInt(mode);
}
public void recordPolygonOffset(float factor, float units) {
buffer.writePolygonOffset(factor, units);
commandCount++;
}
public void recordPolygonMode(int face, int mode) {
buffer.writePolygonMode(face, mode);
commandCount++;
}
// === Three int commands ===
public void recordColorMaterial(int face, int mode) {
buffer.writeColorMaterial(face, mode);
commandCount++;
public void writeStencilFunc(int func, int ref, int mask) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_FUNC);
writeInt(func);
writeInt(ref);
writeInt(mask);
}
public void recordLogicOp(int opcode) {
buffer.writeLogicOp(opcode);
commandCount++;
public void writeStencilOp(int fail, int zfail, int zpass) {
ensureCapacity(16);
writeInt(GLCommand.STENCIL_OP);
writeInt(fail);
writeInt(zfail);
writeInt(zpass);
}
public void recordActiveTexture(int texture) {
buffer.writeActiveTexture(texture);
commandCount++;
public void writeTexParameteri(int target, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.TEX_PARAMETERI);
writeInt(target);
writeInt(pname);
writeInt(param);
}
public void recordUseProgram(int program) {
buffer.writeUseProgram(program);
commandCount++;
}
// === Four int commands ===
public void recordPushAttrib(int mask) {
buffer.writePushAttrib(mask);
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 recordPopAttrib() {
buffer.writePopAttrib();
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 recordFogf(int pname, float param) {
buffer.writeFogf(pname, param);
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 recordFogi(int pname, int param) {
buffer.writeFogi(pname, param);
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 recordHint(int target, int mode) {
buffer.writeHint(target, mode);
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 recordFog(int pname, FloatBuffer params) {
buffer.writeFog(pname, params);
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 recordLightf(int light, int pname, float param) {
buffer.writeLightf(light, pname, param);
commandCount++;
}
// === Float commands ===
public void recordLighti(int light, int pname, int param) {
buffer.writeLighti(light, pname, param);
commandCount++;
public void writePointSize(float size) {
ensureCapacity(8);
writeInt(GLCommand.POINT_SIZE);
writeFloat(size);
}
public void recordLight(int light, int pname, FloatBuffer params) {
buffer.writeLight(light, pname, params);
commandCount++;
public void writeLineWidth(float width) {
ensureCapacity(8);
writeInt(GLCommand.LINE_WIDTH);
writeFloat(width);
}
public void recordLightModelf(int pname, float param) {
buffer.writeLightModelf(pname, param);
commandCount++;
public void writePolygonOffset(float factor, float units) {
ensureCapacity(12);
writeInt(GLCommand.POLYGON_OFFSET);
writeFloat(factor);
writeFloat(units);
}
public void recordLightModeli(int pname, int param) {
buffer.writeLightModeli(pname, param);
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 recordLightModel(int pname, FloatBuffer params) {
buffer.writeLightModel(pname, params);
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 recordMaterialf(int face, int pname, float val) {
buffer.writeMaterialf(face, pname, val);
commandCount++;
public void writeClearDepth(double depth) {
ensureCapacity(12);
writeInt(GLCommand.CLEAR_DEPTH);
writeDouble(depth);
}
public void recordMaterial(int face, int pname, FloatBuffer params) {
buffer.writeMaterial(face, 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 recordClipPlane(int plane, double a, double b, double c, double d) {
buffer.writeClipPlane(plane, a, b, c, d);
commandCount++;
}
// === Mixed int+float commands ===
public void recordStencilFunc(int func, int ref, int mask) {
buffer.writeStencilFunc(func, ref, mask);
commandCount++;
public void writeAlphaFunc(int func, float ref) {
ensureCapacity(12);
writeInt(GLCommand.ALPHA_FUNC);
writeInt(func);
writeFloat(ref);
}
public void recordStencilFuncSeparate(int face, int func, int ref, int mask) {
buffer.writeStencilFuncSeparate(face, func, ref, mask);
commandCount++;
public void writeFogf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.FOGF);
writeInt(pname);
writeFloat(param);
}
public void recordStencilOp(int fail, int zfail, int zpass) {
buffer.writeStencilOp(fail, zfail, zpass);
commandCount++;
public void writeLightf(int light, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTF);
writeInt(light);
writeInt(pname);
writeFloat(param);
}
public void recordStencilOpSeparate(int face, int sfail, int dpfail, int dppass) {
buffer.writeStencilOpSeparate(face, sfail, dpfail, dppass);
commandCount++;
public void writeLightModelf(int pname, float param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELF);
writeInt(pname);
writeFloat(param);
}
public void recordStencilMask(int mask) {
buffer.writeStencilMask(mask);
commandCount++;
public void writeLighti(int light, int pname, int param) {
ensureCapacity(16);
writeInt(GLCommand.LIGHTI);
writeInt(light);
writeInt(pname);
writeInt(param);
}
public void recordStencilMaskSeparate(int face, int mask) {
buffer.writeStencilMaskSeparate(face, mask);
commandCount++;
public void writeLightModeli(int pname, int param) {
ensureCapacity(12);
writeInt(GLCommand.LIGHT_MODELI);
writeInt(pname);
writeInt(param);
}
public void recordCallList(int listId) {
buffer.writeCallList(listId);
commandCount++;
public void writeMaterialf(int face, int pname, float param) {
ensureCapacity(16);
writeInt(GLCommand.MATERIALF);
writeInt(face);
writeInt(pname);
writeFloat(param);
}
public void recordDrawBuffer(int mode) {
buffer.writeDrawBuffer(mode);
commandCount++;
}
public void recordDrawBuffers(int count, int buf) {
buffer.writeDrawBuffers(count, buf);
commandCount++;
}
public void recordDrawBuffers(int count, IntBuffer bufs) {
buffer.writeDrawBuffers(count, bufs);
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 recordDrawArrays(int mode, int start, int count) {
buffer.writeDrawArrays(mode, start, count);
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 recordDrawElements(int mode, int indices_count, int type, long indices_buffer_offset) {
buffer.writeDrawElements(mode, indices_count, type, indices_buffer_offset);
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();
}
}
@@ -2,12 +2,15 @@ package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizons.angelica.glsm.DisplayListManager;
import com.gtnewhorizons.angelica.glsm.recording.commands.DisplayListCommand;
import com.gtnewhorizons.angelica.glsm.recording.commands.IndexedDrawBatch;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.List;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memFree;
@@ -26,24 +29,30 @@ public final class CompiledDisplayList {
* Empty display list singleton. Per OpenGL spec, an empty list is still valid.
* This instance does nothing when rendered and is never deleted (shared singleton).
*/
public static final CompiledDisplayList EMPTY = new CompiledDisplayList(null, null, null);
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) {
public CompiledDisplayList(ByteBuffer commandBuffer, DisplayListCommand[] complexObjects, DisplayListVBO ownedVbos, List<IndexedDrawBatch> indexedBatches) {
this.commandBuffer = commandBuffer;
this.complexObjects = complexObjects;
this.ownedVbos = ownedVbos;
this.indexedBatches = indexedBatches;
}
/**
* 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);
}
}
@@ -51,20 +60,24 @@ public final class CompiledDisplayList {
* Delete all resources held by this display list.
*/
public void delete() {
// Delete complex objects that hold resources
if (complexObjects != null) {
for (Object obj : complexObjects) {
if (obj instanceof DisplayListCommand cmd) {
cmd.delete();
}
for (DisplayListCommand cmd : complexObjects) {
cmd.delete();
}
}
// Close VBOs
// Close VBOs from immediate-mode DisplayListVBOBuilder.
if (ownedVbos != null) {
ownedVbos.delete();
}
// Release shared (VAO, VBO, EBO) triples from IndexedDrawBatchBuilder.
if (indexedBatches != null) {
for (IndexedDrawBatch batch : indexedBatches) {
batch.delete();
}
}
// Free the command buffer (off-heap memory)
if (commandBuffer != null) {
memFree(commandBuffer);
@@ -81,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;
}
@@ -89,6 +102,10 @@ public final class CompiledDisplayList {
return ownedVbos;
}
public List<IndexedDrawBatch> getIndexedBatches() {
return indexedBatches;
}
// === Test inspection methods ===
/**
@@ -129,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);
}
}
@@ -1,6 +1,7 @@
package com.gtnewhorizons.angelica.glsm.recording;
import com.gtnewhorizon.gtnhlib.client.renderer.vao.IVertexArrayObject;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
/**
* A class that stores multiple vertex formats & their corresponding buffers.
@@ -23,6 +24,10 @@ public final class DisplayListVBO {
return vbos;
}
public SubVBO getVBO(int index) {
return vbos[index];
}
public void render(int index) {
vbos[index].render();
}
@@ -33,12 +38,26 @@ public final class DisplayListVBO {
private final int drawMode;
private final int start;
private final int count;
private final int vertexFlags;
public SubVBO(IVertexArrayObject vao, int drawMode, int start, int count) {
public SubVBO(IVertexArrayObject vao, int drawMode, int start, int count, int vertexFlags) {
this.vao = vao;
this.drawMode = drawMode;
this.start = start;
this.count = count;
this.vertexFlags = vertexFlags;
}
public int getStart() {
return start;
}
public int getCount() {
return count;
}
public int getDrawMode() {
return drawMode;
}
public void delete() {
@@ -46,11 +65,15 @@ public final class DisplayListVBO {
}
public void render() {
if (vao == null) return;
vao.bind();
if (count <= 0) { vao.unbind(); return; }
VAOManager.setCurrentVertexFlags(vertexFlags);
vao.draw(drawMode, start, count);
vao.unbind();
}
public IVertexArrayObject getVAO() {
return vao;
}
@@ -68,15 +68,22 @@ public final class DisplayListVBOBuilder {
allBuffers.addAll(drawBuffers);
int size = 0;
for (ByteBuffer buffer : drawBuffers) {
size += buffer.remaining();
size += buffer.limit();
}
vertexCount = format.getVertexCount(size);
}
final int flags = format.getVertexFlags();
if (data.drawMode == GL11.GL_QUADS) {
final IVertexArrayObject indexedVAO = new IndexedVAO(vbo, IndexBuffer.convertQuadsToTrigs(start, start + vertexCount));
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(indexedVAO, GL11.GL_TRIANGLES, 0, vertexCount / 4 * 6);
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(indexedVAO, GL11.GL_TRIANGLES, 0, vertexCount / 4 * 6, flags);
} else if (data.drawMode == GL11.GL_QUAD_STRIP) {
// GL_QUAD_STRIP is removed in core profile; an even-length GL_TRIANGLE_STRIP produces the same quads
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, GL11.GL_TRIANGLE_STRIP, start, vertexCount & ~1, flags);
} else if (data.drawMode == GL11.GL_POLYGON) {
// GL_POLYGON is removed in core profile; convert to GL_TRIANGLE_FAN which is equivalent for convex polygons
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, GL11.GL_TRIANGLE_FAN, start, vertexCount, flags);
} else {
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, data.drawMode, start, vertexCount);
vbos[data.drawIndex] = new DisplayListVBO.SubVBO(vao, data.drawMode, start, vertexCount, flags);
}
@@ -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) ===
@@ -97,8 +94,6 @@ public final class GLCommand {
public static final int CALL_LIST = 102; // [cmd:4][listId:4] = 8 bytes
public static final int DRAW_ARRAYS = 110; // [cmd:4][drawMode:4][start:4][count:4] = 20 bytes
public static final int DRAW_ELEMENTS = 111; // [cmd:4][mode:4][indices_count:4][type:4][indices_buffer_offset:8] = 28 bytes
public static final int DRAW_BUFFER = 112; // [cmd:4][mode:4] = 8 bytes
public static final int DRAW_BUFFERS = 113; // [cmd:4][count:4][bufs:4*8] = 40 bytes (up to 8 buffers)
/**
@@ -169,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";
@@ -219,7 +211,7 @@ public final class GLCommand {
// Three int commands (16 bytes)
case GLCommand.STENCIL_FUNC, GLCommand.STENCIL_OP, GLCommand.TEX_PARAMETERI,
GLCommand.LIGHTF, GLCommand.LIGHTI,
GLCommand.MATERIALF, GLCommand.TEX_PARAMETERF, GLCommand.DRAW_ARRAYS -> 16;
GLCommand.MATERIALF, GLCommand.TEX_PARAMETERF -> 16;
// Four int commands (20 bytes)
case GLCommand.VIEWPORT, GLCommand.BLEND_FUNC, GLCommand.COLOR_MASK,
@@ -227,8 +219,6 @@ public final class GLCommand {
GLCommand.SCISSOR,
GLCommand.COLOR, GLCommand.CLEAR_COLOR, GLCommand.BLEND_COLOR -> 20;
case GLCommand.DRAW_ELEMENTS -> 24;
// DRAW_RANGE_RESTORE: 28 bytes
case GLCommand.DRAW_RANGE_RESTORE -> 28;
@@ -239,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,47 +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.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.Arrays;
import static com.gtnewhorizons.angelica.glsm.backend.BackendManager.RENDER_BACKEND;
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() {
@@ -167,19 +176,14 @@ public final class ImmediateModeRecorder {
.mapToInt(Usage::getAttributeLocation)
.max().orElse(0);
private static final ByteBuffer[] scratchAttribBuffers = new ByteBuffer[NUM_ATTRIBS];
private static final long[] scratchAttribBaseOffsets = new long[NUM_ATTRIBS];
private static final int[] scratchVBOIds = new int[NUM_ATTRIBS];
private static final ByteBuffer[] scratchVBOBuffers = new ByteBuffer[NUM_ATTRIBS];
private static final long[] scratchVBOReadOffsets = new long[NUM_ATTRIBS];
private static final class AttribReader {
ByteBuffer data;
int stride;
int baseOffset;
int size;
int loc;
VertexAttribState.Attrib attrib;
int type;
boolean normalized;
}
private static final AttribReader[] readers = new AttribReader[NUM_ATTRIBS];
@@ -198,167 +202,33 @@ public final class ImmediateModeRecorder {
* Reads vertex data from VBOs or client memory referenced by VertexAttribState and converts to immediate mode geometry.
*/
public static DirectTessellator processDrawArraysFromAttribs(int mode, int first, int count) {
final int readVBOCount = readbackAttribVBOs(first, count);
final AttribSnapshot snap = AttribSnapshot.snapshot(first, count);
if (snap == null) return null;
try {
prepareReaders();
prepareReaders(snap);
if (positionReaderIndex < 0) return null;
return readVerticesFromBuffer(mode, first, count);
} finally {
freeReadbackBuffers(readVBOCount);
snap.free();
}
}
/**
* Process a glDrawElements call during display list recording when a VBO is bound.
*/
public static DirectTessellator processDrawElementsFromVBO(int mode, int indicesCount, int indexType, long indicesOffset, int eboId) {
if (eboId == 0) return null;
final int readVBOCount = readbackAttribVBOs(-1, 0);
try {
prepareReaders();
if (positionReaderIndex < 0) return null;
final int prevEBO = GLStateManager.getBoundEBO();
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, eboId);
final int eboSize = RENDER_BACKEND.getBufferParameteri(GL15.GL_ELEMENT_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (eboSize <= 0) {
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
return null;
}
final int indexSize = VertexAttribState.Attrib.glTypeSizeBytes(indexType);
final int eboReadOffset = (int) indicesOffset;
final int eboReadSize = indicesCount * indexSize;
if (eboReadOffset + eboReadSize > eboSize) {
GLStateManager.LOGGER.warn("[EBO] Read range {}+{} exceeds buffer size {}", eboReadOffset, eboReadSize, eboSize);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
return null;
}
final ByteBuffer eboData = MemoryUtilities.memAlloc(eboReadSize);
RENDER_BACKEND.getBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboReadOffset, eboData);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
try {
return readVerticesFromBufferIndexed(mode, eboData, indexType, 0, indicesCount);
} finally {
MemoryUtilities.memFree(eboData);
}
} finally {
freeReadbackBuffers(readVBOCount);
}
}
/**
* Read data for all enabled attribs into scratchAttribBuffers.
* For VBO-backed attribs, reads VBO data. For client-memory attribs, uses the client pointer directly.
*/
private static int readbackAttribVBOs(int firstVertex, int vertexCount) {
int readVBOCount = 0;
final int prevVBO = GLStateManager.getBoundVBO();
final boolean partialRead = firstVertex >= 0;
for (int i = 0; i < NUM_ATTRIBS; i++) {
scratchAttribBuffers[i] = null;
scratchAttribBaseOffsets[i] = 0;
final VertexAttribState.Attrib a = VertexAttribState.get(i);
if (!a.enabled) continue;
if (a.vboId != 0) {
// VBO-backed attrib — read from GPU
ByteBuffer existing = null;
int existingIdx = -1;
for (int j = 0; j < readVBOCount; j++) {
if (scratchVBOIds[j] == a.vboId) {
existing = scratchVBOBuffers[j];
existingIdx = j;
break;
}
}
if (existing != null) {
scratchAttribBuffers[i] = existing;
scratchAttribBaseOffsets[i] = scratchVBOReadOffsets[existingIdx];
} else {
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, a.vboId);
final int bufferSize = RENDER_BACKEND.getBufferParameteri(GL15.GL_ARRAY_BUFFER, GL15.GL_BUFFER_SIZE);
if (bufferSize <= 0) {
GLStateManager.LOGGER.warn("[VBO Readback] GL_BUFFER_SIZE={} for VBO {}", bufferSize, a.vboId);
continue;
}
final long readOffset;
final int readSize;
if (partialRead) {
final int stride = a.effectiveStride();
long rangeStart = a.offset + (long) firstVertex * stride;
long rangeEnd = a.offset + (long) (firstVertex + vertexCount - 1) * stride + (long) a.size * a.typeSizeBytes();
for (int k = 0; k < NUM_ATTRIBS; k++) {
if (k == i) continue;
final VertexAttribState.Attrib ak = VertexAttribState.get(k);
if (!ak.enabled || ak.vboId != a.vboId) continue;
final int ks = ak.effectiveStride();
rangeStart = Math.min(rangeStart, ak.offset + (long) firstVertex * ks);
rangeEnd = Math.max(rangeEnd, ak.offset + (long) (firstVertex + vertexCount - 1) * ks
+ (long) ak.size * ak.typeSizeBytes());
}
rangeStart = Math.max(0, rangeStart);
rangeEnd = Math.min(bufferSize, rangeEnd);
readOffset = rangeStart;
readSize = (int) (rangeEnd - rangeStart);
} else {
readOffset = 0;
readSize = bufferSize;
}
if (readSize <= 0) continue;
final ByteBuffer buf = MemoryUtilities.memAlloc(readSize);
RENDER_BACKEND.getBufferSubData(GL15.GL_ARRAY_BUFFER, readOffset, buf);
scratchAttribBuffers[i] = buf;
scratchAttribBaseOffsets[i] = readOffset;
scratchVBOIds[readVBOCount] = a.vboId;
scratchVBOReadOffsets[readVBOCount] = readOffset;
scratchVBOBuffers[readVBOCount] = buf;
readVBOCount++;
}
} else if (a.clientPointer != null) {
// Client-memory attrib — use pointer directly (no allocation needed)
scratchAttribBuffers[i] = a.clientPointer;
}
}
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
return readVBOCount;
}
private static void freeReadbackBuffers(int readVBOCount) {
for (int i = 0; i < readVBOCount; i++) {
MemoryUtilities.memFree(scratchVBOBuffers[i]);
scratchVBOBuffers[i] = null;
}
}
private static void prepareReaders() {
private static void prepareReaders(AttribSnapshot snap) {
nonPositionCount = 0;
positionReaderIndex = -1;
for (int i = 0; i < NUM_ATTRIBS; i++) {
final ByteBuffer buf = scratchAttribBuffers[i];
if (buf == null) continue;
final VertexAttribState.Attrib a = VertexAttribState.get(i);
final AttribSnapshot.AttribDesc d = snap.get(i);
if (d == null) continue;
final AttribReader r = readers[i];
r.data = buf;
r.stride = a.effectiveStride();
r.baseOffset = (int) (a.offset - scratchAttribBaseOffsets[i]);
r.size = a.size;
r.data = d.readBuffer();
r.stride = d.effectiveStride();
r.baseOffset = (int) (d.offset() - d.readBufferBaseOffset());
r.size = d.size();
r.loc = i;
r.attrib = a;
r.type = d.type();
r.normalized = d.normalized();
if (i == LOC_POSITION) {
positionReaderIndex = i;
@@ -379,18 +249,6 @@ public final class ImmediateModeRecorder {
return end();
}
private static DirectTessellator readVerticesFromBufferIndexed(int mode, ByteBuffer eboData, int indexType, int eboOffset, int indicesCount) {
begin(mode);
final DirectTessellator t = tessellator();
for (int i = 0; i < indicesCount; i++) {
final int index = readIndex(eboData, indexType, eboOffset, i);
readVertexFromBuffer(t, index);
}
return end();
}
public static int readIndex(ByteBuffer ebo, int indexType, int eboOffset, int i) {
return switch (indexType) {
case GL11.GL_UNSIGNED_BYTE -> ebo.get(eboOffset + i) & 0xFF;
@@ -411,39 +269,43 @@ public final class ImmediateModeRecorder {
final AttribReader r = readers[positionReaderIndex];
final int base = r.baseOffset + vertexIndex * r.stride;
t.addVertex(
r.attrib.readComponent(r.data, base, 0),
r.attrib.readComponent(r.data, base, 1),
(r.size >= 3) ? r.attrib.readComponent(r.data, base, 2) : 0.0f);
readComponent(r, base, 0),
readComponent(r, base, 1),
(r.size >= 3) ? readComponent(r, base, 2) : 0.0f);
}
}
private static void emitAttrib(DirectTessellator t, AttribReader r, int base) {
if (r.loc == LOC_NORMAL) {
t.setNormal(
r.attrib.readComponent(r.data, base, 0),
r.attrib.readComponent(r.data, base, 1),
r.attrib.readComponent(r.data, base, 2));
readComponent(r, base, 0),
readComponent(r, base, 1),
readComponent(r, base, 2));
} else if (r.loc == LOC_UV0) {
t.setTextureUV(
r.attrib.readComponent(r.data, base, 0),
(r.size >= 2) ? r.attrib.readComponent(r.data, base, 1) : 0.0f);
readComponent(r, base, 0),
(r.size >= 2) ? readComponent(r, base, 1) : 0.0f);
} else if (r.loc == LOC_UV1) {
if (r.attrib.type == GL11.GL_SHORT) {
if (r.type == GL11.GL_SHORT) {
final int s = r.data.getShort(base) & 0xFFFF;
final int u = (r.size >= 2) ? (r.data.getShort(base + 2) & 0xFFFF) : 0;
t.setBrightness((u << 16) | s);
} else if (r.attrib.type == GL11.GL_FLOAT) {
} else if (r.type == GL11.GL_FLOAT) {
final int s = (int) r.data.getFloat(base);
final int u = (r.size >= 2) ? (int) r.data.getFloat(base + 4) : 0;
t.setBrightness((u << 16) | (s & 0xFFFF));
}
} else if (r.loc == LOC_COLOR) {
final float cr = r.attrib.readComponent(r.data, base, 0);
final float cg = r.attrib.readComponent(r.data, base, 1);
final float cb = r.attrib.readComponent(r.data, base, 2);
final float ca = (r.size >= 4) ? r.attrib.readComponent(r.data, base, 3) : 1.0f;
final float cr = readComponent(r, base, 0);
final float cg = readComponent(r, base, 1);
final float cb = readComponent(r, base, 2);
final float ca = (r.size >= 4) ? readComponent(r, base, 3) : 1.0f;
t.setColorRGBA((int) (cr * 255.0f), (int) (cg * 255.0f), (int) (cb * 255.0f), (int) (ca * 255.0f));
}
}
private static float readComponent(AttribReader r, int base, int component) {
return VAOManager.Attrib.readComponent(r.type, r.normalized, r.data, base, component);
}
}
@@ -0,0 +1,64 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import java.util.Arrays;
public final class AttribLayoutKey {
final int[] locations;
final int[] sizes;
final int[] types;
final boolean[] normalized;
private final int[] offsets;
private final int stride;
private final int hash;
public AttribLayoutKey(int[] locations, int[] sizes, int[] types, boolean[] normalized) {
this.locations = locations;
this.sizes = sizes;
this.types = types;
this.normalized = normalized;
final int n = locations.length;
final int[] off = new int[n];
int base = 0;
int maxAlign = 1;
for (int i = 0; i < n; i++) {
final int typeSize = GLTypes.sizeBytes(types[i]);
if (typeSize > maxAlign) maxAlign = typeSize;
base = (base + typeSize - 1) & -typeSize;
off[i] = base;
base += sizes[i] * typeSize;
}
this.offsets = off;
this.stride = (base + maxAlign - 1) & -maxAlign;
int h = Arrays.hashCode(locations);
h = 31 * h + Arrays.hashCode(sizes);
h = 31 * h + Arrays.hashCode(types);
h = 31 * h + Arrays.hashCode(normalized);
this.hash = h;
}
public int offset(int i) {
return offsets[i];
}
public int stride() {
return stride;
}
@Override
public boolean equals(Object o) {
if (!(o instanceof AttribLayoutKey k)) return false;
return Arrays.equals(locations, k.locations)
&& Arrays.equals(sizes, k.sizes)
&& Arrays.equals(types, k.types)
&& Arrays.equals(normalized, k.normalized);
}
@Override
public int hashCode() {
return hash;
}
}
@@ -0,0 +1,37 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
public final class BatchedIndexedDrawCmd implements DisplayListCommand {
private int sharedVAO;
private int drawMode;
private int indexCount;
private int indexType;
private long indexByteOffset;
public void fill(int sharedVAO, int drawMode, int indexCount, int indexType, long indexByteOffset) {
this.sharedVAO = sharedVAO;
this.drawMode = drawMode;
this.indexCount = indexCount;
this.indexType = indexType;
this.indexByteOffset = indexByteOffset;
}
public int getSharedVAO() { return sharedVAO; }
public int getDrawMode() { return drawMode; }
public int getIndexCount() { return indexCount; }
public int getIndexType() { return indexType; }
public long getIndexByteOffset() { return indexByteOffset; }
@Override
public void execute() {
if (sharedVAO == 0) return;
GLStateManager.glBindVertexArray(sharedVAO);
GLStateManager.glDrawElements(drawMode, indexCount, indexType, indexByteOffset);
}
@Override
public String toString() {
return "BatchedIndexedDrawCmd(vao=" + sharedVAO + ", mode=" + drawMode + ", count=" + indexCount + ", type=" + indexType + ", offset=" + indexByteOffset + ")";
}
}
@@ -0,0 +1,34 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
public final class IndexedDrawBatch {
private int sharedVAO;
private int sharedVBO;
private int sharedEBO;
private boolean deleted;
public IndexedDrawBatch(int sharedVAO, int sharedVBO, int sharedEBO) {
this.sharedVAO = sharedVAO;
this.sharedVBO = sharedVBO;
this.sharedEBO = sharedEBO;
}
public int getSharedVAO() { return sharedVAO; }
public int getSharedVBO() { return sharedVBO; }
public int getSharedEBO() { return sharedEBO; }
public void delete() {
if (deleted) return;
deleted = true;
if (sharedVAO != 0) GLStateManager.glDeleteVertexArrays(sharedVAO);
if (sharedVBO != 0) GLStateManager.glDeleteBuffers(sharedVBO);
if (sharedEBO != 0) GLStateManager.glDeleteBuffers(sharedEBO);
sharedVAO = sharedVBO = sharedEBO = 0;
}
@Override
public String toString() {
return "IndexedDrawBatch(vao=" + sharedVAO + ", vbo=" + sharedVBO + ", ebo=" + sharedEBO + ")";
}
}
@@ -0,0 +1,171 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memAddress0;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memCopy;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
public final class IndexedDrawBatchBuilder {
private final List<IndexedDrawCapture> captures = new ArrayList<>();
public boolean isEmpty() {
return captures.isEmpty();
}
public void add(IndexedDrawCapture capture) {
captures.add(capture);
}
public List<IndexedDrawCapture> getCaptures() {
return captures;
}
public List<IndexedDrawBatch> build() {
final List<IndexedDrawBatch> out = new ArrayList<>();
if (captures.isEmpty()) return out;
final int prevVAO = GLStateManager.getBoundVAO();
final int prevVBO = GLStateManager.getBoundVBO();
final int prevEBO = GLStateManager.getBoundEBO();
final List<GroupResult> results = new ArrayList<>();
boolean success = false;
try {
final Object2ObjectLinkedOpenHashMap<AttribLayoutKey, List<IndexedDrawCapture>> groups = new Object2ObjectLinkedOpenHashMap<>();
for (IndexedDrawCapture c : captures) {
groups.computeIfAbsent(c.layoutKey, k -> new ArrayList<>()).add(c);
}
for (List<IndexedDrawCapture> group : groups.values()) {
results.add(buildGroup(group));
}
// Placeholder fill is deferred until every group's GL work has succeeded.
for (GroupResult r : results) r.commit();
for (GroupResult r : results) out.add(r.batch);
success = true;
} finally {
if (!success) {
for (GroupResult r : results) r.batch.delete();
}
GLStateManager.glBindVertexArray(prevVAO);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, prevEBO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, prevVBO);
}
return out;
}
private record GroupResult(IndexedDrawBatch batch, List<IndexedDrawCapture> captures,
int[] drawModes, int[] indexCounts, long[] indexOffsets) {
void commit() {
final int sharedVAO = batch.getSharedVAO();
for (int i = 0; i < captures.size(); i++) {
captures.get(i).placeholder.fill(sharedVAO, drawModes[i], indexCounts[i], GL11.GL_UNSIGNED_INT, indexOffsets[i]);
}
}
}
private static GroupResult buildGroup(List<IndexedDrawCapture> group) {
long totalVertexBytes = 0;
long totalIndexBytes = 0;
for (IndexedDrawCapture c : group) {
totalVertexBytes += (long) c.vertexCount * c.layoutKey.stride();
totalIndexBytes += (long) c.indexCount * 4; // always GL_UNSIGNED_INT
}
if (totalVertexBytes > Integer.MAX_VALUE || totalIndexBytes > Integer.MAX_VALUE) {
throw new IllegalStateException("batch exceeds 2GiB — split needed");
}
int sharedVAO = 0;
int sharedVBO = 0;
int sharedEBO = 0;
ByteBuffer eboData = null;
boolean success = false;
final int n = group.size();
final int[] drawModes = new int[n];
final int[] indexCounts = new int[n];
final long[] indexOffsets = new long[n];
try {
sharedVAO = GLStateManager.glGenVertexArrays();
sharedVBO = GLStateManager.glGenBuffers();
sharedEBO = GLStateManager.glGenBuffers();
eboData = MemoryUtilities.memAlloc((int) totalIndexBytes);
GLStateManager.glBindVertexArray(sharedVAO);
GLStateManager.glBindBuffer(GL15.GL_ARRAY_BUFFER, sharedVBO);
GLStateManager.glBufferData(GL15.GL_ARRAY_BUFFER, totalVertexBytes, GL15.GL_STATIC_DRAW);
int vboBytePos = 0;
int eboBytePos = 0;
int vertexBase = 0;
final long eboAddr = memAddress0(eboData);
for (int g = 0; g < n; g++) {
final IndexedDrawCapture c = group.get(g);
final int vBytes = c.vertexCount * c.layoutKey.stride();
GLStateManager.glBufferSubData(GL15.GL_ARRAY_BUFFER, vboBytePos, c.vertexData);
final long srcIdxAddr = memAddress0(c.indexData);
final int idxBytes = c.indexCount * 4;
if (vertexBase == 0) {
// First capture (or a group that starts at 0) — indices are already
// rebased to [0..vertexCount) in IndexedDrawCapture, so a raw bulk copy
// is correct and strictly faster than the per-index loop.
memCopy(srcIdxAddr, eboAddr + eboBytePos, idxBytes);
} else {
final long dstBase = eboAddr + eboBytePos;
for (int i = 0; i < c.indexCount; i++) {
memPutInt(dstBase + i * 4L, memGetInt(srcIdxAddr + i * 4L) + vertexBase);
}
}
drawModes[g] = c.drawMode;
indexCounts[g] = c.indexCount;
indexOffsets[g] = eboBytePos;
vboBytePos += vBytes;
eboBytePos += idxBytes;
vertexBase += c.vertexCount;
}
eboData.position(0).limit((int) totalIndexBytes);
GLStateManager.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, sharedEBO);
GLStateManager.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, eboData, GL15.GL_STATIC_DRAW);
final AttribLayoutKey layout = group.get(0).layoutKey;
final int stride = layout.stride();
for (int i = 0; i < layout.locations.length; i++) {
final int loc = layout.locations[i];
final int size = layout.sizes[i];
final int type = layout.types[i];
GLStateManager.glVertexAttribPointer(loc, size, type, layout.normalized[i], stride, layout.offset(i));
GLStateManager.glEnableVertexAttribArray(loc);
}
success = true;
return new GroupResult(new IndexedDrawBatch(sharedVAO, sharedVBO, sharedEBO),
group, drawModes, indexCounts, indexOffsets);
} finally {
if (eboData != null) MemoryUtilities.memFree(eboData);
if (!success) {
if (sharedVAO != 0) GLStateManager.glDeleteVertexArrays(sharedVAO);
if (sharedVBO != 0) GLStateManager.glDeleteBuffers(sharedVBO);
if (sharedEBO != 0) GLStateManager.glDeleteBuffers(sharedEBO);
}
}
}
}
@@ -0,0 +1,264 @@
package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
import com.gtnewhorizons.angelica.glsm.QuadConverter;
import com.gtnewhorizons.angelica.glsm.ffp.VAOManager;
import com.gtnewhorizons.angelica.glsm.recording.AttribSnapshot;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL15;
import java.nio.ByteBuffer;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memGetLong;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutInt;
import static com.gtnewhorizon.gtnhlib.bytebuf.MemoryUtilities.memPutLong;
/** {@link #create} returns null on any recoverable validation failure; callers must not record the placeholder. */
public final class IndexedDrawCapture {
public final AttribLayoutKey layoutKey;
public final int drawMode;
public final int vertexCount;
public final int indexCount;
public final ByteBuffer vertexData;
public final ByteBuffer indexData;
public final BatchedIndexedDrawCmd placeholder;
private boolean freed;
public IndexedDrawCapture(AttribLayoutKey layoutKey, int drawMode, int vertexCount, int indexCount,
ByteBuffer vertexData, ByteBuffer indexData,
BatchedIndexedDrawCmd placeholder) {
this.layoutKey = layoutKey;
this.drawMode = drawMode;
this.vertexCount = vertexCount;
this.indexCount = indexCount;
this.vertexData = vertexData;
this.indexData = indexData;
this.placeholder = placeholder;
}
public void freeBuffers() {
if (freed) return;
freed = true;
if (vertexData != null) MemoryUtilities.memFree(vertexData);
if (indexData != null) MemoryUtilities.memFree(indexData);
}
public boolean isFreed() {
return freed;
}
public static IndexedDrawCapture create(int mode, int indicesCount, int srcIndexType, long indicesOffset, int eboId) {
if (indicesCount == 0) return null;
if (eboId == 0) {
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) {
GLStateManager.warnOnce("quads-misaligned", "[IndexedDrawCapture] GL_QUADS with indicesCount={} not a multiple of 4 — skipping", indicesCount);
return null;
}
final int srcIndexSize = GLTypes.sizeBytes(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 = GLTypes.sizeBytes(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 {
final long minMax = QuadConverter.scanMinMaxIndex(indexSrcAddr, srcIndexType, indicesCount);
if (minMax == -1L) {
GLStateManager.warnOnce("min-max", "[IndexedDrawCapture] failed to determine min/max vertex for indexType={}", srcIndexType);
return null;
}
final int minVtx = (int) (minMax & 0xFFFFFFFFL);
final int maxVtx = (int) (minMax >>> 32);
final int vertexCount = maxVtx - minVtx + 1;
snap = AttribSnapshot.snapshot(minVtx, vertexCount);
if (snap == null) return null;
int enabledCount = 0;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
if (snap.get(i) != null) enabledCount++;
}
if (enabledCount == 0) {
GLStateManager.warnOnce("no-attribs", "[IndexedDrawCapture] no enabled attributes — skipping");
return null;
}
final int[] locations = new int[enabledCount];
final int[] sizes = new int[enabledCount];
final int[] types = new int[enabledCount];
final boolean[] normalized = new boolean[enabledCount];
int k = 0;
for (int i = 0; i < VAOManager.MAX_ATTRIBS; i++) {
final AttribSnapshot.AttribDesc d = snap.get(i);
if (d == null) continue;
locations[k] = i;
sizes[k] = d.size();
types[k] = d.type();
normalized[k] = d.normalized();
k++;
}
final AttribLayoutKey layoutKey = new AttribLayoutKey(locations, sizes, types, normalized);
final int tightStride = layoutKey.stride();
ByteBuffer sharedSrcBuffer = null;
long sharedSrcBase = 0;
int sharedSrcStride = -1;
boolean tightSrc = true;
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();
sharedSrcBase = MemoryUtilities.memAddress0(sharedSrcBuffer) + relOffset + (long) minVtx * sharedSrcStride;
if (relOffset != layoutKey.offset(0)) tightSrc = false;
} else if (d.readBuffer() != sharedSrcBuffer || d.effectiveStride() != sharedSrcStride || relOffset != layoutKey.offset(a)) {
tightSrc = false;
}
}
if (tightSrc && sharedSrcStride != tightStride) tightSrc = false;
vertexData = MemoryUtilities.memAlloc(vertexCount * tightStride);
final long dstAddr = MemoryUtilities.memAddress0(vertexData);
if (tightSrc) {
MemoryUtilities.memCopy(sharedSrcBase, dstAddr, (long) vertexCount * tightStride);
} else {
for (int a = 0; a < enabledCount; a++) {
final AttribSnapshot.AttribDesc d = snap.get(locations[a]);
final int attribBytes = d.size() * d.typeSizeBytes();
final int srcStride = d.effectiveStride();
final long srcStart = MemoryUtilities.memAddress0(d.readBuffer()) + (d.offset() - d.readBufferBaseOffset()) + (long) minVtx * srcStride;
final long dstStart = dstAddr + layoutKey.offset(a);
copyStrided(srcStart, srcStride, dstStart, tightStride, attribBytes, vertexCount);
}
}
final int bakedDrawMode;
final int bakedIndexCount;
if (mode == GL11.GL_QUADS) {
final int quadCount = indicesCount / 4;
bakedIndexCount = quadCount * 6;
indexData = MemoryUtilities.memAlloc(bakedIndexCount * 4);
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(indexSrcAddr, srcIndexType, MemoryUtilities.memAddress0(indexData), GL11.GL_UNSIGNED_INT, indicesCount, minVtx);
bakedDrawMode = mode;
}
final BatchedIndexedDrawCmd placeholder = new BatchedIndexedDrawCmd();
final IndexedDrawCapture out = new IndexedDrawCapture(layoutKey, bakedDrawMode, vertexCount, bakedIndexCount, vertexData, indexData, placeholder);
success = true;
return out;
} finally {
if (snap != null) snap.free();
if (!success) {
if (vertexData != null) MemoryUtilities.memFree(vertexData);
if (indexData != null) MemoryUtilities.memFree(indexData);
}
}
}
/**
* Copy {@code attribBytes} per vertex from {@code src} (stride {@code srcStride}) to
* {@code dst} (stride {@code dstStride}) for {@code vertexCount} vertices.
*
* <p>Specializes on the common attribute sizes (4 / 8 / 12 / 16) so the hot path is
* one or two {@code memPut} calls per vertex instead of a JNI-ish {@code memCopy}
* dispatch. Falls through to {@code memCopy} for exotic sizes.
*/
private static void copyStrided(long src, int srcStride, long dst, int dstStride, int attribBytes, int vertexCount) {
switch (attribBytes) {
case 4 -> {
for (int v = 0; v < vertexCount; v++) {
memPutInt(dst, memGetInt(src));
src += srcStride; dst += dstStride;
}
}
case 8 -> {
for (int v = 0; v < vertexCount; v++) {
memPutLong(dst, memGetLong(src));
src += srcStride; dst += dstStride;
}
}
case 12 -> {
for (int v = 0; v < vertexCount; v++) {
memPutLong(dst, memGetLong(src));
memPutInt(dst + 8, memGetInt(src + 8));
src += srcStride; dst += dstStride;
}
}
case 16 -> {
for (int v = 0; v < vertexCount; v++) {
memPutLong(dst, memGetLong(src));
memPutLong(dst + 8, memGetLong(src + 8));
src += srcStride; dst += dstStride;
}
}
default -> {
for (int v = 0; v < vertexCount; v++) {
MemoryUtilities.memCopy(src, dst, attribBytes);
src += srcStride; dst += dstStride;
}
}
}
}
}
@@ -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,18 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
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 +27,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), GLTypes.name(type), dataStr, unpack);
}
}
@@ -2,15 +2,18 @@ package com.gtnewhorizons.angelica.glsm.recording.commands;
import com.gtnewhorizons.angelica.glsm.GLDebug;
import com.gtnewhorizons.angelica.glsm.GLStateManager;
import com.gtnewhorizons.angelica.glsm.GLTypes;
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 +27,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), GLTypes.name(type), dataStr, unpack);
}
}
@@ -2,22 +2,29 @@ package com.gtnewhorizons.angelica.glsm.redirect;
import com.google.common.collect.ImmutableSet;
import com.gtnewhorizon.gtnhlib.asm.ClassConstantPoolParser;
import com.gtnewhorizons.angelica.config.SystemProperties;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.InvokeDynamicInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* Core GL call redirector for GLSM. Rewrites bytecode so that GL calls go through {@code GLStateManager} for state tracking and backend abstraction.
@@ -27,12 +34,107 @@ import java.util.Set;
*/
public class GLSMRedirector {
private static final boolean ASSERT_MAIN_THREAD = Boolean.getBoolean("angelica.assertMainThread");
private static final boolean LOG_SPAM = Boolean.getBoolean("angelica.redirectorLogspam");
private static final boolean LOG_SPAM = SystemProperties.REDIRECTOR_LOGSPAM;
private static final Logger LOGGER = LogManager.getLogger("GLSMRedirector");
private static final String LWJGL_GL_PACKAGE = "org/lwjgl/opengl/";
private static final Set<String> DEBUG_OWNERS = ImmutableSet.of(
"org/lwjgl/opengl/ARBDebugOutput",
"org/lwjgl/opengl/AMDDebugOutput"
);
private static final Set<String> DEBUG_METHODS = ImmutableSet.of(
"glDebugMessageCallback",
"glDebugMessageControl",
"glDebugMessageInsert",
"glGetDebugMessageLog",
"glObjectLabel",
"glGetObjectLabel",
"glObjectPtrLabel",
"glGetObjectPtrLabel",
"glPushDebugGroup",
"glPopDebugGroup"
);
private static final Set<String> UNMAPPED_SEEN = ConcurrentHashMap.newKeySet();
private static final Set<String> DEBUG_SEEN = ConcurrentHashMap.newKeySet();
private static boolean detectionEnabled() {
return SystemProperties.UNMAPPED_GL.detects();
}
private static final String GLSYNC_TYPE = "Lorg/lwjgl/opengl/GLSync;";
private static boolean isDebugCall(String owner, String name) {
if (!owner.startsWith(LWJGL_GL_PACKAGE)) return false;
return DEBUG_OWNERS.contains(owner) || DEBUG_METHODS.contains(name);
}
private static boolean isUnmappedGlCall(String owner, String name, String desc) {
if (!owner.startsWith(LWJGL_GL_PACKAGE)) return false;
if (!name.startsWith("gl") && !name.startsWith("ngl")) return false;
if (desc.contains(GLSYNC_TYPE)) return false;
return true;
}
private static boolean hasLwjglTypedDescriptor(String desc) {
return desc.contains("Lorg/lwjgl/");
}
private static final Set<String> AWARE_NON_DISPATCHING = ImmutableSet.of(
"org/lwjgl/opengl/GL getCapabilities",
"org/lwjgl/opengl/GL setCapabilities",
"org/lwjgl/opengl/GL getFunctionProvider",
"org/lwjgl/opengl/GL create",
"org/lwjgl/opengl/GLCapabilities getAddressBuffer",
"org/lwjgl/opengl/GLCapabilities initialize",
"org/lwjgl/opengl/GLDebugMessageCallback create",
"org/lwjgl/opengl/GLDebugMessageCallback createSafe",
"org/lwjgl/opengl/GLDebugMessageCallback getMessage"
);
private static boolean isAwareNonDispatching(String owner, String name) {
return AWARE_NON_DISPATCHING.contains(owner + " " + name);
}
private static void reportAwareUnroutable(String className, String owner, String name, String desc) {
if (!detectionEnabled()) return;
if (UNMAPPED_SEEN.add("aware " + owner + "." + name + desc)) {
LOGGER.warn("Aware-unroutable GL call {}.{}{} in {}; needs an awareOwnerRedirects entry (see AwareGLDebugShim)", owner, name, desc, className);
}
if (SystemProperties.UNMAPPED_GL.failsAwareUnroutable()) {
throw new IllegalStateException("Aware-unroutable GL call " + owner + "." + name + desc + " in " + className);
}
}
private static void reportUnmapped(String className, String owner, String name, String desc) {
if (!detectionEnabled()) return;
if (UNMAPPED_SEEN.add(owner + "." + name + desc)) {
LOGGER.warn("Unmapped GL call {}.{}{} in {} reached the redirector; on a non-GL backend this will crash at runtime.", owner, name, desc, className);
}
if (SystemProperties.UNMAPPED_GL.failsUnmapped()) {
throw new IllegalStateException("Unmapped GL call " + owner + "." + name + desc + " in " + className);
}
}
private static void reportDebugPassthrough(String className, String owner, String name, String desc) {
if (!detectionEnabled()) return;
if (DEBUG_SEEN.add(owner + "." + name + desc)) {
LOGGER.info("Debug-output call {}.{}{} in {} bypasses the backend (reaches real GL; unsupported on the SDL backend).", owner, name, desc, className);
}
}
private static final String Drawable = "org/lwjgl/opengl/Drawable";
private static final String SharedDrawable = "org/lwjgl/opengl/SharedDrawable";
private static final String Display = "org/lwjgl/opengl/Display";
private static boolean isDrawableOwner(String owner) {
return owner.startsWith(Drawable) || owner.equals(SharedDrawable);
}
private static final String GLStateManager = "com/gtnewhorizons/angelica/glsm/GLStateManager";
private static final String AwareGLDebugShim = "com/gtnewhorizons/angelica/sdlgpu/compat/AwareGLDebugShim";
private static final Map<String, String> awareOwnerRedirects = new HashMap<>();
private static final String GL_PREFIX = "org/lwjgl/opengl/GL";
private static final String ARBVertexArrayObject = "org/lwjgl/opengl/ARBVertexArrayObject";
private static final String APPLEVertexArrayObject = "org/lwjgl/opengl/APPLEVertexArrayObject";
@@ -43,19 +145,30 @@ 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";
private static final String ARBFramebufferObject = "org/lwjgl/opengl/ARBFramebufferObject";
private static final String EXTFramebufferObject = "org/lwjgl/opengl/EXTFramebufferObject";
private static final String ARBVertexShader = "org/lwjgl/opengl/ARBVertexShader";
private static final String ARBOcclusionQuery = "org/lwjgl/opengl/ARBOcclusionQuery";
private static final String ARBDrawElementsBaseVertex = "org/lwjgl/opengl/ARBDrawElementsBaseVertex";
private static final String ARBMultiDrawIndirect = "org/lwjgl/opengl/ARBMultiDrawIndirect";
private static final String ARBUniformBufferObject = "org/lwjgl/opengl/ARBUniformBufferObject";
private static final String EXTGpuShader4 = "org/lwjgl/opengl/EXTGpuShader4";
private static final String EXTGPUShader4 = "org/lwjgl/opengl/EXTGPUShader4";
private static final String ARBTimerQuery = "org/lwjgl/opengl/ARBTimerQuery";
// Redirect VAO related calls from NHLib
private static final String UniversalVAO = "com/gtnewhorizon/gtnhlib/client/opengl/UniversalVAO";
private static final String MinecraftClient = "net.minecraft.client";
private static final String SplashProgress = "cpw.mods.fml.client.SplashProgress";
private static final Set<String> ExcludedMinecraftMainThreadChecks = ImmutableSet.of(
"startGame", "func_71384_a",
"initializeTextures", "func_77474_a"
);
private static final Map<String, Map<String, String>> methodRedirects = new HashMap<>(32);
private static final Map<String, String> glMethodRedirects = new HashMap<>(256);
private static final Map<String, String> glDescRedirects = new HashMap<>();
private static final Map<Integer, String> glCapRedirects = new HashMap<>();
private static final Map<String, String> typeRedirects = new HashMap<>();
private static final ClassConstantPoolParser cstPoolParser;
@@ -68,17 +181,20 @@ public class GLSMRedirector {
};
static {
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_ALPHA_TEST, "AlphaTest");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_BLEND, "Blend");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_DEPTH_TEST, "DepthTest");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_CULL_FACE, "Cull");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_LIGHTING, "Lighting");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_TEXTURE_2D, "Texture");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_FOG, "Fog");
glCapRedirects.put(org.lwjgl.opengl.GL12.GL_RESCALE_NORMAL, "RescaleNormal");
glCapRedirects.put(org.lwjgl.opengl.GL11.GL_SCISSOR_TEST, "ScissorTest");
glCapRedirects.put(GL11.GL_ALPHA_TEST, "AlphaTest");
glCapRedirects.put(GL11.GL_BLEND, "Blend");
glCapRedirects.put(GL11.GL_DEPTH_TEST, "DepthTest");
glCapRedirects.put(GL11.GL_CULL_FACE, "Cull");
glCapRedirects.put(GL11.GL_LIGHTING, "Lighting");
glCapRedirects.put(GL11.GL_TEXTURE_2D, "Texture");
glCapRedirects.put(GL11.GL_FOG, "Fog");
glCapRedirects.put(GL12.GL_RESCALE_NORMAL, "RescaleNormal");
glCapRedirects.put(GL11.GL_SCISSOR_TEST, "ScissorTest");
final var gl11 = RedirectMap.newMap()
// glEnable/Disable - Special cased in GLSMRedirector, but included here for the external API
.add("glEnable")
.add("glDisable")
.add("glAlphaFunc")
.add("glBegin")
.add("glBindTexture")
@@ -104,6 +220,7 @@ public class GLSMRedirector {
.add("glGenLists")
.add("glGenTextures")
.add("glIsList")
.add("glIsTexture")
.add("glDeleteTextures")
.add("glDepthFunc")
.add("glDepthMask")
@@ -119,8 +236,17 @@ public class GLSMRedirector {
.add("glFogi")
.add("glFrustum")
.add("glGetBoolean")
.add("glGetClipPlane")
.add("glGetDouble")
.add("glGetDoublev", "glGetDouble")
.add("glGetFloat")
.add("glGetFloatv", "glGetFloat")
.add("glGetInteger")
.add("glGetIntegerv", "glGetInteger")
.add("glGetIntegeri")
.add("glGetIntegeri_v")
.add("glGetBooleani")
.add("glGetBooleani_v")
.add("glGetLight")
.add("glGetMaterial")
.add("glGetTexLevelParameteri")
@@ -141,6 +267,7 @@ public class GLSMRedirector {
.add("glListBase")
.add("glLoadIdentity")
.add("glLoadMatrix")
.add("glLoadMatrixf", "glLoadMatrix")
.add("glLogicOp")
.add("glMatrixMode")
.add("glMultMatrix")
@@ -222,6 +349,7 @@ public class GLSMRedirector {
.add("glPolygonStipple")
.add("glAccum")
.add("glReadBuffer")
.add("glReadPixels")
.add("glSampleCoverage")
.add("glScissor")
.add("glStencilFunc")
@@ -259,7 +387,8 @@ public class GLSMRedirector {
final var gl12 = RedirectMap.newMap()
.add("glTexImage3D")
.add("glTexSubImage3D")
.add("glCopyTexSubImage3D");
.add("glCopyTexSubImage3D")
.add("glDrawRangeElements");
final var gl13 = RedirectMap.newMap()
.add("glActiveTexture")
.add("glSampleCoverage")
@@ -270,18 +399,33 @@ public class GLSMRedirector {
final var gl14 = RedirectMap.newMap()
.add("glBlendFuncSeparate", "tryBlendFuncSeparate")
.add("glBlendColor")
.add("glBlendEquation");
.add("glBlendEquation")
.add("glMultiDrawArrays")
.add("glPointParameterf")
.add("glPointParameteri");
final var gl15 = RedirectMap.newMap()
.add("glGenBuffers")
.add("glBindBuffer")
.add("glDeleteBuffers")
.add("glBufferData")
.add("nglBufferData")
.add("glBufferSubData")
.add("nglBufferSubData")
.add("glMapBuffer")
.add("glUnmapBuffer")
.add("glGetBufferSubData")
.add("nglGetBufferSubData")
.add("glGetBufferParameteri")
.add("glIsBuffer");
.add("glGetBufferParameter")
.add("glGetBufferParameteriv")
.add("glIsBuffer")
.add("glIsQuery")
.add("glGenQueries")
.add("glDeleteQueries")
.add("glBeginQuery")
.add("glEndQuery")
.add("glGetQueryObjectui")
.add("glGetQueryObjecti");
final var gl20 = RedirectMap.newMap()
.add("glBlendEquationSeparate")
.add("glDrawBuffers")
@@ -290,6 +434,7 @@ public class GLSMRedirector {
.add("glStencilOpSeparate")
.add("glUseProgram")
.add("glShaderSource")
.add("nglShaderSource")
.add("glLinkProgram")
.add("glDeleteProgram")
.add("glCreateShader")
@@ -300,6 +445,8 @@ public class GLSMRedirector {
.add("glValidateProgram")
.add("glGetUniformLocation")
.add("glGetAttribLocation")
.add("glIsShader")
.add("glIsProgram")
.add("glUniform1f")
.add("glUniform2f")
.add("glUniform3f")
@@ -312,9 +459,14 @@ public class GLSMRedirector {
.add("glUniform2")
.add("glUniform3")
.add("glUniform4")
.add("glUniform1fv", "glUniform1")
.add("glUniform3fv", "glUniform3")
.add("glUniform4fv", "glUniform4")
.add("glUniformMatrix2")
.add("glUniformMatrix3")
.add("glUniformMatrix4")
.add("glUniformMatrix3fv", "glUniformMatrix3")
.add("glUniformMatrix4fv", "glUniformMatrix4")
.add("glDeleteShader")
.add("glGetShaderi")
.add("glGetShaderInfoLog")
@@ -331,11 +483,14 @@ public class GLSMRedirector {
.add("glGetUniform")
.add("glVertexAttribPointer")
.add("glEnableVertexAttribArray")
.add("glDisableVertexAttribArray");
.add("glDisableVertexAttribArray")
.add("glGetActiveAttrib");
final var gl30 = RedirectMap.newMap()
.add("glGenVertexArrays")
.add("glBindVertexArray")
.add("glDeleteVertexArrays")
.add("glIsVertexArray")
.add("glBindFragDataLocation")
.add("glBindFramebuffer")
.add("glDeleteFramebuffers")
.add("glGenFramebuffers")
@@ -344,18 +499,39 @@ public class GLSMRedirector {
.add("glVertexAttribIPointer")
.add("glGenerateMipmap")
.add("glGetFramebufferAttachmentParameteri")
.add("glBlitFramebuffer");
.add("glBlitFramebuffer")
.add("glMapBufferRange")
.add("glFlushMappedBufferRange")
.add("glGenRenderbuffers")
.add("glDeleteRenderbuffers")
.add("glBindRenderbuffer")
.add("glRenderbufferStorage")
.add("glRenderbufferStorageMultisample")
.add("glFramebufferRenderbuffer")
.add("glIsFramebuffer")
.add("glIsRenderbuffer")
.add("glBindBufferBase");
final var gl31 = RedirectMap.newMap()
.add("glDrawElementsInstanced");
.add("glDrawElementsInstanced")
.add("glDrawArraysInstanced")
.add("glCopyBufferSubData")
.add("glPrimitiveRestartIndex")
.add("glGetUniformBlockIndex")
.add("glUniformBlockBinding");
final var gl32 = RedirectMap.newMap()
.add("glFramebufferTexture");
.add("glFramebufferTexture")
.add("glDrawRangeElementsBaseVertex")
.add("glDrawElementsBaseVertex");
final var gl33 = RedirectMap.newMap()
.add("glGenSamplers")
.add("glDeleteSamplers")
.add("glIsSampler")
.add("glBindSampler")
.add("glSamplerParameteri")
.add("glSamplerParameterf")
.add("glVertexAttribDivisor");
.add("glVertexAttribDivisor")
.add("glQueryCounter")
.add("glGetQueryObjectui64");
final var gl42 = RedirectMap.newMap()
.add("glBindImageTexture")
.add("glMemoryBarrier")
@@ -363,15 +539,25 @@ public class GLSMRedirector {
final var gl43 = RedirectMap.newMap()
.add("glDispatchCompute")
.add("glClearBufferSubData")
.add("glClearBufferData")
.add("glBindVertexBuffer")
.add("glVertexAttribFormat")
.add("glVertexAttribIFormat")
.add("glVertexAttribBinding");
.add("glVertexAttribBinding")
.add("glMultiDrawElementsIndirect");
final var gl44 = RedirectMap.newMap()
.add("glBufferStorage")
.add("glClearTexImage");
final var gl45 = RedirectMap.newMap()
.add("glCreateBuffers")
.add("glNamedBufferData")
.add("glNamedBufferSubData");
final var gl46 = RedirectMap.newMap()
.add("glPolygonOffsetClamp")
.add("glMultiDrawArraysIndirectCount")
.add("glMultiDrawElementsIndirectCount")
.add("glSpecializeShader");
// Merge all GL version maps into the flat lookup
glMethodRedirects.putAll(gl11);
glMethodRedirects.putAll(gl12);
glMethodRedirects.putAll(gl13);
@@ -385,6 +571,8 @@ public class GLSMRedirector {
glMethodRedirects.putAll(gl42);
glMethodRedirects.putAll(gl43);
glMethodRedirects.putAll(gl44);
glMethodRedirects.putAll(gl45);
glMethodRedirects.putAll(gl46);
// MINECRAFT
methodRedirects.put(OpenGlHelper, RedirectMap.newMap()
@@ -404,10 +592,73 @@ public class GLSMRedirector {
// ARB
methodRedirects.put(ARBMultiTexture, RedirectMap.newMap()
.add("glActiveTextureARB")
.add("glClientActiveTextureARB", "glClientActiveTexture")
.add("glMultiTexCoord2fARB", "glMultiTexCoord2f")
);
methodRedirects.put(ARBVertexShader, RedirectMap.newMap()
.add("glGetAttribLocationARB", "glGetAttribLocation")
);
methodRedirects.put(ARBFramebufferObject, RedirectMap.newMap()
.add("glBindFramebuffer")
.add("glDeleteFramebuffers")
.add("glGenFramebuffers")
.add("glCheckFramebufferStatus")
.add("glFramebufferTexture2D")
.add("glBindRenderbuffer")
.add("glDeleteRenderbuffers")
.add("glGenRenderbuffers")
.add("glRenderbufferStorage")
.add("glFramebufferRenderbuffer")
);
methodRedirects.put(EXTFramebufferObject, RedirectMap.newMap()
.add("glBindFramebufferEXT", "glBindFramebuffer")
.add("glDeleteFramebuffersEXT", "glDeleteFramebuffers")
.add("glGenFramebuffersEXT", "glGenFramebuffers")
.add("glCheckFramebufferStatusEXT", "glCheckFramebufferStatus")
.add("glFramebufferTexture2DEXT", "glFramebufferTexture2D")
.add("glBindRenderbufferEXT", "glBindRenderbuffer")
.add("glDeleteRenderbuffersEXT", "glDeleteRenderbuffers")
.add("glGenRenderbuffersEXT", "glGenRenderbuffers")
.add("glRenderbufferStorageEXT", "glRenderbufferStorage")
.add("glFramebufferRenderbufferEXT", "glFramebufferRenderbuffer")
);
methodRedirects.put(ARBOcclusionQuery, RedirectMap.newMap()
.add("glGenQueriesARB", "glGenQueries")
.add("glBeginQueryARB", "glBeginQuery")
.add("glEndQueryARB", "glEndQuery")
.add("glGetQueryObjectuARB", "glGetQueryObjectui")
);
methodRedirects.put(ARBTimerQuery, RedirectMap.newMap()
.add("glQueryCounter")
.add("glGetQueryObjectui64")
);
glDescRedirects.put("org/lwjgl/opengl/GL32C glFenceSync(II)J", "glFenceSync");
glDescRedirects.put("org/lwjgl/opengl/GL32C glClientWaitSync(JIJ)I", "glClientWaitSync");
glDescRedirects.put("org/lwjgl/opengl/GL32C glWaitSync(JIJ)V", "glWaitSync");
glDescRedirects.put("org/lwjgl/opengl/GL32C glGetSynci(JILjava/nio/IntBuffer;)I", "glGetSynci");
glDescRedirects.put("org/lwjgl/opengl/GL32C glDeleteSync(J)V", "glDeleteSync");
glDescRedirects.put("org/lwjgl/opengl/GL15C nglMapBuffer(II)J", "nglMapBuffer");
glDescRedirects.put("org/lwjgl/opengl/GL32C nglMultiDrawElementsBaseVertex(IJIJIJ)V", "nglMultiDrawElementsBaseVertex");
for (String o : new String[]{"org/lwjgl/opengl/KHRDebug", "org/lwjgl/opengl/GL43", "org/lwjgl/opengl/GL43C"}) {
glDescRedirects.put(o + " glObjectLabel(IILjava/lang/CharSequence;)V", "glObjectLabel");
glDescRedirects.put(o + " glGetObjectLabel(III)Ljava/lang/String;", "glGetObjectLabel");
glDescRedirects.put(o + " glPushDebugGroup(IILjava/lang/CharSequence;)V", "glPushDebugGroup");
glDescRedirects.put(o + " glPopDebugGroup()V", "glPopDebugGroup");
glDescRedirects.put(o + " glDebugMessageInsert(IIIILjava/lang/CharSequence;)V", "glDebugMessageInsert");
glDescRedirects.put(o + " glDebugMessageControl(IIILjava/nio/IntBuffer;Z)V", "glDebugMessageControl");
glDescRedirects.put(o + " glGetDebugMessageLog(ILjava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/IntBuffer;Ljava/nio/ByteBuffer;)I", "glGetDebugMessageLog");
glDescRedirects.put(o + " glDebugMessageCallback(Lorg/lwjgl/opengl/KHRDebugCallback;)V", "glDebugMessageCallback");
awareOwnerRedirects.put(o + " glDebugMessageCallback(Lorg/lwjgl/opengl/GLDebugMessageCallbackI;J)V", AwareGLDebugShim);
}
awareOwnerRedirects.put("org/lwjgl/opengl/GLUtil setupDebugMessageCallback()Lorg/lwjgl/system/Callback;", AwareGLDebugShim);
awareOwnerRedirects.put("org/lwjgl/opengl/GLUtil setupDebugMessageCallback(Ljava/io/PrintStream;)Lorg/lwjgl/system/Callback;", AwareGLDebugShim);
methodRedirects.put(ARBShaderObjects, RedirectMap.newMap()
.add("glUseProgramObjectARB", "glUseProgram")
.add("glShaderSourceARB", "glShaderSource")
.add("nglShaderSourceARB", "nglShaderSource")
.add("glLinkProgramARB", "glLinkProgram")
.add("glCreateShaderObjectARB", "glCreateShader")
.add("glCompileShaderARB", "glCompileShader")
@@ -445,14 +696,65 @@ public class GLSMRedirector {
methodRedirects.put(ARBVertexArrayObject, RedirectMap.newMap()
.add("glBindVertexArray")
.add("glDeleteVertexArrays")
.add("glGenVertexArrays")
);
methodRedirects.put(ARBDrawElementsBaseVertex, RedirectMap.newMap()
.add("glDrawElementsBaseVertex")
);
methodRedirects.put(ARBMultiDrawIndirect, RedirectMap.newMap()
.add("glMultiDrawElementsIndirect")
);
final var uniformBufferObject = RedirectMap.newMap()
.add("glBindBufferBase")
.add("glGetUniformBlockIndex")
.add("glUniformBlockBinding");
methodRedirects.put(ARBUniformBufferObject, uniformBufferObject);
final var gpuShader4 = RedirectMap.newMap()
.add("glVertexAttribIPointerEXT", "glVertexAttribIPointer");
methodRedirects.put(EXTGpuShader4, gpuShader4);
methodRedirects.put(EXTGPUShader4, gpuShader4);
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()
.add("glBindVertexArrayAPPLE", "glBindVertexArray")
.add("glGenVertexArraysAPPLE", "glGenVertexArrays")
.add("glDeleteVertexArraysAPPLE", "glDeleteVertexArrays")
);
// GTNHLib VAO
@@ -466,12 +768,15 @@ public class GLSMRedirector {
.add("gluPerspective")
.add("gluLookAt")
.add("gluPickMatrix")
.add("gluUnProject")
);
methodRedirects.put(GLU, RedirectMap.newMap()
.add("gluPerspective")
.add("gluLookAt")
.add("gluOrtho2D")
.add("gluPickMatrix")
.add("gluBuild2DMipmaps")
.add("gluErrorString")
);
// Quadric type replacements
@@ -480,38 +785,52 @@ public class GLSMRedirector {
typeRedirects.put("org/lwjgl/util/glu/Disk", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaDisk");
typeRedirects.put("org/lwjgl/util/glu/PartialDisk", "com/gtnewhorizons/angelica/glsm/compat/lwjgl/AngelicaPartialDisk");
// Build constant pool scanner
final List<String> stringsToSearch = new ArrayList<>(32);
stringsToSearch.add(GL_PREFIX);
stringsToSearch.add(LWJGL_GL_PACKAGE);
stringsToSearch.add(Drawable);
stringsToSearch.add(SharedDrawable);
stringsToSearch.add(Display);
stringsToSearch.addAll(typeRedirects.keySet());
for (String key : methodRedirects.keySet()) {
stringsToSearch.add(key);
}
stringsToSearch.addAll(methodRedirects.keySet());
cstPoolParser = new ClassConstantPoolParser(stringsToSearch.toArray(new String[0]));
}
/** Core exclusions that GLSM always requires. */
public String[] getCoreExclusions() {
return CORE_EXCLUSIONS.clone();
}
public static String getGLPrefix() { return GL_PREFIX; }
public static String getTargetClassName() { return GLStateManager; }
/** Key: original method name, value: GLStateManager method name. */
public static Map<String, String> getGLPrefixMethodRedirects() {
return Collections.unmodifiableMap(glMethodRedirects);
}
/** Key: internal class name, value: per-method redirect map. */
public static Map<String, Map<String, String>> getNamedClassMethodRedirects() {
return Collections.unmodifiableMap(methodRedirects);
}
public boolean shouldTransform(byte[] basicClass) {
return cstPoolParser.find(basicClass, true);
}
/** @return Was the class changed? */
public boolean transformClassNode(String transformedName, ClassNode cn) {
return transformClassNode(transformedName, cn, false);
}
public boolean transformClassNode(String transformedName, ClassNode cn, boolean lwjgl3Aware) {
boolean changed = false;
final boolean isOpenGlHelper = transformedName.equals("net.minecraft.client.renderer.OpenGlHelper");
for (MethodNode mn : cn.methods) {
for (MethodNode mn : (List<MethodNode>) cn.methods) {
if (isOpenGlHelper && (mn.name.equals("glBlendFunc") || mn.name.equals("func_148821_a"))) {
continue;
}
boolean redirectInMethod = false;
for (AbstractInsnNode node : mn.instructions.toArray()) {
if (node instanceof TypeInsnNode tNode) {
// Redirect NEW and CHECKCAST for quadric classes
if (tNode.getOpcode() == Opcodes.NEW || tNode.getOpcode() == Opcodes.CHECKCAST) {
final String redirect = typeRedirects.get(tNode.desc);
if (redirect != null) {
@@ -553,8 +872,7 @@ public class GLSMRedirector {
mn.instructions.remove(prevNode);
}
changed = true;
redirectInMethod = true;
} else if (mNode.name.equals("makeCurrent") && mNode.owner.startsWith(Drawable)) {
} else if (mNode.name.equals("makeCurrent") && isDrawableOwner(mNode.owner)) {
mNode.setOpcode(Opcodes.INVOKESTATIC);
mNode.owner = GLStateManager;
mNode.desc = "(L" + Drawable + ";)V";
@@ -563,23 +881,63 @@ public class GLSMRedirector {
if (LOG_SPAM) {
LOGGER.info("Redirecting call in {} to GLStateManager.makeCurrent()", transformedName);
}
} else if (mNode.name.equals("releaseContext") && isDrawableOwner(mNode.owner)) {
mNode.setOpcode(Opcodes.INVOKESTATIC);
mNode.owner = GLStateManager;
mNode.desc = "(L" + Drawable + ";)V";
mNode.itf = false;
changed = true;
if (LOG_SPAM) {
LOGGER.info("Redirecting call in {} to GLStateManager.releaseContext()", transformedName);
}
} else if (mNode.name.equals("swapBuffers") && mNode.owner.equals(Display) && mNode.desc.equals("()V")) {
mNode.owner = GLStateManager;
changed = true;
if (LOG_SPAM) {
LOGGER.info("Redirecting call in {} to GLStateManager.swapBuffers()", transformedName);
}
} else if (mNode.name.equals("update") && mNode.owner.equals(Display) && (mNode.desc.equals("()V") || mNode.desc.equals("(Z)V"))) {
mNode.owner = GLStateManager;
mNode.name = "updateDisplay";
changed = true;
if (LOG_SPAM) {
LOGGER.info("Redirecting call in {} to GLStateManager.updateDisplay{}", transformedName, mNode.desc);
}
} else if (mNode.name.equals("isCurrent") && mNode.owner.equals(Display) && mNode.desc.equals("()Z")) {
mNode.owner = GLStateManager;
changed = true;
if (LOG_SPAM) {
LOGGER.info("Redirecting call in {} to GLStateManager.isCurrent()", transformedName);
}
} 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;
String glsmName = glDescRedirects.get(mNode.owner + " " + mNode.name + mNode.desc);
if (glsmName == null) {
final Map<String, String> redirects = mNode.owner.startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(mNode.owner);
glsmName = redirects != null ? redirects.get(mNode.name) : null;
}
final String awareOwner = lwjgl3Aware ? awareOwnerRedirects.get(mNode.owner + " " + mNode.name + mNode.desc) : null;
if (awareOwner != null) {
if (LOG_SPAM) {
LOGGER.info("Redirecting aware call in {} from {}.{}{} to {}.{}", transformedName, mNode.owner, mNode.name, mNode.desc, awareOwner, mNode.name);
}
mNode.owner = awareOwner;
changed = true;
} else 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;
} else if (lwjgl3Aware && mNode.owner.startsWith(LWJGL_GL_PACKAGE) && hasLwjglTypedDescriptor(mNode.desc) && !isAwareNonDispatching(mNode.owner, mNode.name)) {
reportAwareUnroutable(transformedName, mNode.owner, mNode.name, mNode.desc);
} else if (isDebugCall(mNode.owner, mNode.name)) {
reportDebugPassthrough(transformedName, mNode.owner, mNode.name, mNode.desc);
} else if (isUnmappedGlCall(mNode.owner, mNode.name, mNode.desc)) {
reportUnmapped(transformedName, mNode.owner, mNode.name, mNode.desc);
}
}
// Redirect <init> calls for quadric classes
if (mNode.getOpcode() == Opcodes.INVOKESPECIAL && mNode.name.equals("<init>")) {
final String redirect = typeRedirects.get(mNode.owner);
if (redirect != null) {
@@ -590,11 +948,32 @@ public class GLSMRedirector {
changed = true;
}
}
} else if (node instanceof InvokeDynamicInsnNode dynNode) {
// Redirect method handles in invokedynamic bootstrap arguments
for (int i = 0; i < dynNode.bsmArgs.length; i++) {
if (!(dynNode.bsmArgs[i] instanceof Handle handle)) continue;
String glsmName = glDescRedirects.get(handle.getOwner() + " " + handle.getName() + handle.getDesc());
if (glsmName == null) {
final Map<String, String> redirects = handle.getOwner().startsWith(GL_PREFIX) ? glMethodRedirects : methodRedirects.get(handle.getOwner());
glsmName = redirects != null ? redirects.get(handle.getName()) : null;
}
if (glsmName == null) {
if (isDebugCall(handle.getOwner(), handle.getName())) {
reportDebugPassthrough(transformedName, handle.getOwner(), handle.getName(), handle.getDesc());
} else if (isUnmappedGlCall(handle.getOwner(), handle.getName(), handle.getDesc())) {
reportUnmapped(transformedName, handle.getOwner(), handle.getName(), handle.getDesc());
}
continue;
}
if (LOG_SPAM) {
final String shortOwner = handle.getOwner().substring(handle.getOwner().lastIndexOf("/") + 1);
LOGGER.info("Redirecting invokedynamic handle in {} from {}.{}{} to GLStateManager.{}{}", transformedName, shortOwner, handle.getName(), handle.getDesc(), glsmName, handle.getDesc());
}
dynNode.bsmArgs[i] = new Handle(handle.getTag(), GLStateManager, glsmName, handle.getDesc());
changed = true;
}
}
}
if (ASSERT_MAIN_THREAD && redirectInMethod && !transformedName.startsWith(SplashProgress) && !(transformedName.startsWith(MinecraftClient) && ExcludedMinecraftMainThreadChecks.contains(mn.name))) {
mn.instructions.insert(new MethodInsnNode(Opcodes.INVOKESTATIC, GLStateManager, "assertMainThread", "()V", false));
}
}
return changed;
@@ -0,0 +1,306 @@
package com.gtnewhorizons.angelica.glsm.shader;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.opengl.GL20;
import org.taumc.glsl.grammar.GLSLLexer;
import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.grammar.GLSLPreParser;
import org.taumc.glsl.grammar.GLSLParserBaseListener;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public final class GlslVulkanPreprocess {
private static final Logger LOGGER = LogManager.getLogger("GlslVulkanPreprocess");
public static final String SAMPLER_RENAMED = "angelica_sampler_renamed";
public static final String SAMPLERLESS_EXTENSION = "#extension GL_EXT_samplerless_texture_functions : require";
private static final int MAX_VS_INPUT_LOCATIONS = 16;
private static final int CACHE_MAX = 256;
private static final Object2ObjectLinkedOpenHashMap<CacheKey, Result> CACHE = new Object2ObjectLinkedOpenHashMap<>();
private GlslVulkanPreprocess() {}
private record CacheKey(String source, int glShaderType, boolean separateReadOnlyImages) {}
public record Result(String rewrittenSource, Set<String> boolUniforms, Set<String> explicitVsInputs) {}
/** Replace {@code [startIdx, stopIdx]} with {@code replacement}. */
public record Edit(int startIdx, int stopIdx, String replacement) {}
public static int startIdx(ParserRuleContext ctx) {
return ctx.getStart().getStartIndex();
}
public static int stopIdx(ParserRuleContext ctx) {
return ctx.getStop().getStopIndex();
}
/** Apply non-overlapping edits to the source in one pass. */
public static String applyEdits(String src, List<Edit> edits) {
if (edits.isEmpty()) return src;
final List<Edit> sorted;
if (edits.size() == 1) {
sorted = edits;
} else {
sorted = new ArrayList<>(edits);
sorted.sort(Comparator.comparingInt(Edit::startIdx).thenComparingInt(Edit::stopIdx));
}
final StringBuilder sb = new StringBuilder(src.length());
int cursor = 0;
for (Edit e : sorted) {
if (e.startIdx < cursor) continue;
sb.append(src, cursor, e.startIdx);
sb.append(e.replacement);
cursor = e.stopIdx + 1;
}
sb.append(src, cursor, src.length());
return sb.toString();
}
/** Returns {@code null} on parse failure — callers treat that the same as a shaderc compile failure. */
public static @Nullable Result run(String source, int glShaderType, String debugName, boolean separateReadOnlyImages) {
final CacheKey key = new CacheKey(source, glShaderType, separateReadOnlyImages);
synchronized (CACHE) {
final Result hit = CACHE.getAndMoveToFirst(key);
if (hit != null) return hit;
}
final GLSLParser.Translation_unitContext root;
try {
root = GlslTransformUtils.parseFullQuiet(source);
} catch (Exception e) {
LOGGER.warn("glsl-transformation-lib parse failed for '{}': {}", debugName, e.getMessage());
return null;
}
final List<Edit> edits = new ArrayList<>();
final Metadata meta = collectEdits(source, root, glShaderType, debugName, separateReadOnlyImages, edits);
final Result out = new Result(applyEdits(source, edits), meta.boolUniforms(), meta.explicitVsInputs());
synchronized (CACHE) {
CACHE.putAndMoveToFirst(key, out);
while (CACHE.size() > CACHE_MAX) CACHE.removeLast();
}
return out;
}
public record Metadata(Set<String> boolUniforms, Set<String> explicitVsInputs) {}
public static Metadata collectEdits(String source, GLSLParser.Translation_unitContext root, int glShaderType, String debugName, boolean separateReadOnlyImages, List<Edit> edits) {
final boolean isVertex = glShaderType == GL20.GL_VERTEX_SHADER;
final Set<String> bools = new HashSet<>();
final Set<String> explicitInputs = new HashSet<>();
final Set<String> readOnlyImages = new HashSet<>();
final boolean[] needsSamplerless = { false };
final int[] maxExplicitLoc = { -1 };
final List<Integer> unlocatedVsInputStarts = new ArrayList<>();
ParseTreeWalker.DEFAULT.walk(new GLSLParserBaseListener() {
@Override
public void enterInit_declarator_list(GLSLParser.Init_declarator_listContext ctx) {
final GLSLParser.Single_declarationContext single = ctx.single_declaration();
if (single == null || single.fully_specified_type() == null) return;
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
final GLSLParser.Type_qualifierContext tq = fst.type_qualifier();
if (tq == null) return;
boolean hasUniform = false, hasIn = false, hasLocation = false, hasReadonly = false;
int locValue = -1;
int bindingValue = -1;
for (GLSLParser.Single_type_qualifierContext stq : tq.single_type_qualifier()) {
if (stq.storage_qualifier() != null) {
final String s = stq.storage_qualifier().getText();
if ("uniform".equals(s)) hasUniform = true;
else if ("in".equals(s)) hasIn = true;
else if ("readonly".equals(s)) hasReadonly = true;
} else if (stq.layout_qualifier() != null) {
for (GLSLParser.Layout_qualifier_idContext id : stq.layout_qualifier().layout_qualifier_id_list().layout_qualifier_id()) {
if (id.IDENTIFIER() == null) continue;
final String qualifier = id.IDENTIFIER().getText();
if ("location".equals(qualifier)) {
hasLocation = true;
if (id.constant_expression() != null) {
try { locValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
}
} else if ("binding".equals(qualifier) && id.constant_expression() != null) {
try { bindingValue = Integer.parseInt(id.constant_expression().getText()); } catch (NumberFormatException ignored) {}
}
}
}
}
final boolean boolDecl = hasUniform && isBool(fst);
final boolean explicitInputDecl = isVertex && hasIn && hasLocation;
if (single.typeless_declaration() != null) {
handleDeclarator(single.typeless_declaration(), hasUniform, boolDecl, explicitInputDecl, bools, explicitInputs, edits);
}
for (GLSLParser.Typeless_declarationContext td : ctx.typeless_declaration()) {
handleDeclarator(td, hasUniform, boolDecl, explicitInputDecl, bools, explicitInputs, edits);
}
if (separateReadOnlyImages && hasUniform && fst.type_specifier() != null) {
final String typeText = fst.type_specifier().getText();
if (separateTextureType(typeText) != null) {
needsSamplerless[0] = true;
} else if (hasReadonly) {
final String asTexture = separateTextureTypeForImage(typeText);
if (asTexture != null) {
needsSamplerless[0] = true;
collectDeclaredNames(single, ctx, readOnlyImages);
final String binding = bindingValue >= 0 ? "layout(binding = " + bindingValue + ") " : "";
edits.add(new Edit(startIdx(fst), stopIdx(fst), binding + "uniform " + asTexture));
}
}
}
if (isVertex && hasIn) {
if (hasLocation && locValue >= 0) {
if (locValue > maxExplicitLoc[0]) maxExplicitLoc[0] = locValue;
} else if (!hasLocation) {
unlocatedVsInputStarts.add(ctx.getStart().getStartIndex());
}
}
}
@Override
public void enterPostfix_expression(GLSLParser.Postfix_expressionContext ctx) {
if (readOnlyImages.isEmpty() || ctx.LEFT_PAREN() == null || ctx.RIGHT_PAREN() == null) return;
final GLSLParser.Function_call_parametersContext params = ctx.function_call_parameters();
if (params == null || params.assignment_expression() == null || params.assignment_expression().isEmpty()) return;
if (!readOnlyImages.contains(params.assignment_expression(0).getText())) return;
final ParseTree callee = ctx.getChild(0);
if (!(callee instanceof ParserRuleContext calleeCtx)) return;
final String replacement = switch (calleeCtx.getText()) {
case "imageLoad" -> "texelFetch";
case "imageSize" -> "textureSize";
default -> null;
};
if (replacement == null) return;
edits.add(new Edit(startIdx(calleeCtx), stopIdx(calleeCtx), replacement));
final int rparen = ctx.RIGHT_PAREN().getSymbol().getStartIndex();
edits.add(new Edit(rparen, rparen - 1, ", 0"));
}
@Override
public void visitTerminal(TerminalNode node) {
final Token tok = node.getSymbol();
if (tok.getType() != GLSLLexer.IDENTIFIER) return;
final String text = tok.getText();
switch (text) {
case "sampler" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), SAMPLER_RENAMED));
case "gl_VertexID" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), "gl_VertexIndex"));
case "gl_InstanceID" -> edits.add(new Edit(tok.getStartIndex(), tok.getStopIndex(), "gl_InstanceIndex"));
default -> {}
}
}
}, root);
if (needsSamplerless[0]) {
final int at = versionDirectiveEnd(source);
if (at >= 0) edits.add(new Edit(at, at - 1, "\n" + SAMPLERLESS_EXTENSION));
}
if (maxExplicitLoc[0] >= 0 && !unlocatedVsInputStarts.isEmpty()) {
int next = maxExplicitLoc[0] + 1;
int dropped = 0;
for (int start : unlocatedVsInputStarts) {
if (next >= MAX_VS_INPUT_LOCATIONS) { dropped++; continue; }
edits.add(new Edit(start, start - 1, "layout(location = " + next + ") "));
next++;
}
if (dropped > 0) {
LOGGER.warn("'{}': dropped {} VS input location-conflict fixes (would exceed {} attribute slots)", debugName, dropped, MAX_VS_INPUT_LOCATIONS);
}
}
return new Metadata(bools, explicitInputs);
}
public static void clearCache() {
synchronized (CACHE) { CACHE.clear(); }
}
private static void collectDeclaredNames(GLSLParser.Single_declarationContext single, GLSLParser.Init_declarator_listContext ctx, Set<String> out) {
if (single.typeless_declaration() != null && single.typeless_declaration().IDENTIFIER() != null) {
out.add(single.typeless_declaration().IDENTIFIER().getText());
}
for (GLSLParser.Typeless_declarationContext td : ctx.typeless_declaration()) {
if (td.IDENTIFIER() != null) out.add(td.IDENTIFIER().getText());
}
}
private static @Nullable String separateTextureTypeForImage(String typeText) {
final String dim = switch (typeText) {
case "image1D", "iimage1D", "uimage1D" -> "1D";
case "image2D", "iimage2D", "uimage2D" -> "2D";
case "image3D", "iimage3D", "uimage3D" -> "3D";
default -> null;
};
if (dim == null) return null;
return typeText.charAt(0) == 'i' ? "itexture" + dim : typeText.charAt(0) == 'u' ? "utexture" + dim : "texture" + dim;
}
private static @Nullable String separateTextureType(String typeText) {
return switch (typeText) {
case "texture1D", "itexture1D", "utexture1D",
"texture2D", "itexture2D", "utexture2D",
"texture3D", "itexture3D", "utexture3D" -> typeText;
default -> null;
};
}
private static int versionDirectiveEnd(String source) {
final GLSLPreParser.Translation_unitContext pre;
try {
pre = GlslTransformUtils.parsePreQuiet(source);
} catch (Exception e) {
return -1;
}
final GLSLPreParser.Version_directiveContext ctx = firstVersionDirective(pre);
return ctx == null || ctx.getStop() == null ? -1 : ctx.getStop().getStopIndex() + 1;
}
private static GLSLPreParser.@Nullable Version_directiveContext firstVersionDirective(ParseTree node) {
if (node instanceof GLSLPreParser.Version_directiveContext v) return v;
for (int i = 0; i < node.getChildCount(); i++) {
final GLSLPreParser.Version_directiveContext found = firstVersionDirective(node.getChild(i));
if (found != null) return found;
}
return null;
}
private static void handleDeclarator(GLSLParser.Typeless_declarationContext td, boolean hasUniform, boolean asBool, boolean asInput, Set<String> bools, Set<String> inputs, List<Edit> edits) {
if (td.IDENTIFIER() == null) return;
final String name = td.IDENTIFIER().getText();
if (asBool) bools.add(name);
if (asInput) inputs.add(name);
if (hasUniform && td.EQUAL() != null && td.initializer() != null) {
edits.add(new Edit(td.EQUAL().getSymbol().getStartIndex(), td.initializer().getStop().getStopIndex(), ""));
}
}
private static boolean isBool(GLSLParser.Fully_specified_typeContext fst) {
return fst.type_specifier() != null && fst.type_specifier().type_specifier_nonarray() != null && "bool".equals(fst.type_specifier().type_specifier_nonarray().getText());
}
}
@@ -1,6 +1,6 @@
// This file is based on code from Sodium by JellySquid, licensed under the LGPLv3 license.
package net.coderbot.iris.gl.shader;
package com.gtnewhorizons.angelica.glsm.shader;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
@@ -0,0 +1,183 @@
package com.gtnewhorizons.angelica.glsm.shader;
import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.util.shaderc.Shaderc;
import org.lwjgl.system.MemoryUtil;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicInteger;
import static org.lwjgl.system.MemoryUtil.memFree;
import static org.lwjgl.system.MemoryUtil.memUTF8;
/** GLSL -> SPIR-V via shaderc */
@Lwjgl3Aware
public final class SpirvCompiler {
private static final Logger LOGGER = LogManager.getLogger("SpirvCompiler");
private static final ThreadLocal<Long> COMPILER = ThreadLocal.withInitial(() -> {
final long c = Shaderc.shaderc_compiler_initialize();
if (c == 0L) throw new IllegalStateException("shaderc_compiler_initialize failed");
return c;
});
private static final Path FAILURE_DIR = Paths.get("shaderc_failures");
private static final AtomicInteger FAILURE_COUNTER = new AtomicInteger();
private static final int CACHE_MAX = 256;
private static final Object2ObjectLinkedOpenHashMap<CacheKey, byte[]> CACHE = new Object2ObjectLinkedOpenHashMap<>();
private record CacheKey(String source, int shaderKind, Options opts) {}
private SpirvCompiler() {}
public record Options(int targetEnv, int envVersion, boolean vulkanRulesRelaxed, boolean autoBindUniforms, boolean autoMapLocations, boolean autoCombinedImageSampler, int forcedVersion, int forcedProfile) {
public static Options vulkanRelaxed() {
return new Options(Shaderc.shaderc_target_env_vulkan, Shaderc.shaderc_env_version_vulkan_1_0, true, true, true, true, 0, 0);
}
public static Options vulkanForced460Core() {
return new Options(Shaderc.shaderc_target_env_vulkan, Shaderc.shaderc_env_version_vulkan_1_0, true, true, true, false, 460, Shaderc.shaderc_profile_core);
}
}
public record Result(@Nullable ByteBuffer spirv, @Nullable String error, @Nullable Path dumpPath) {}
public static Result compile(String source, int shaderKind, String debugName, Options opts) {
final String cleanSrc = source.indexOf('\0') >= 0 ? source.replace("\0", "") : source;
final CacheKey key = new CacheKey(cleanSrc, shaderKind, opts);
final byte[] cached;
synchronized (CACHE) {
cached = CACHE.getAndMoveToFirst(key);
}
if (cached != null) {
final ByteBuffer copy = MemoryUtil.memAlloc(cached.length);
copy.put(cached).flip();
return new Result(copy, null, null);
}
final long compiler = COMPILER.get();
final long optionsHandle = Shaderc.shaderc_compile_options_initialize();
if (optionsHandle == 0L) {
return new Result(null, "shaderc_compile_options_initialize failed", null);
}
try {
configureOptions(optionsHandle, opts);
ByteBuffer srcBuf = null, nameBuf = null, entryBuf = null;
long result = 0L;
try {
srcBuf = memUTF8(cleanSrc, false);
nameBuf = memUTF8(debugName == null ? "shader" : debugName);
entryBuf = memUTF8("main");
result = Shaderc.shaderc_compile_into_spv(compiler, srcBuf, shaderKind, nameBuf, entryBuf, optionsHandle);
if (result == 0L) {
return new Result(null, "shaderc_compile_into_spv returned null", null);
}
final int status = Shaderc.shaderc_result_get_compilation_status(result);
if (status != Shaderc.shaderc_compilation_status_success) {
final String msg = Shaderc.shaderc_result_get_error_message(result);
final Path dumpPath = writeFailureDump(debugName, shaderKind, msg, cleanSrc);
return new Result(null, msg == null ? "(no error message)" : msg, dumpPath);
}
final ByteBuffer spirv = Shaderc.shaderc_result_get_bytes(result);
if (spirv == null || spirv.remaining() == 0) {
return new Result(null, "shaderc returned empty SPIR-V", null);
}
final ByteBuffer copy = MemoryUtil.memAlloc(spirv.remaining());
copy.put(spirv);
copy.flip();
final byte[] heap = new byte[copy.remaining()];
copy.duplicate().get(heap);
synchronized (CACHE) {
CACHE.putAndMoveToFirst(key, heap);
while (CACHE.size() > CACHE_MAX) CACHE.removeLast();
}
return new Result(copy, null, null);
} finally {
if (result != 0L) Shaderc.shaderc_result_release(result);
if (entryBuf != null) memFree(entryBuf);
if (nameBuf != null) memFree(nameBuf);
if (srcBuf != null) memFree(srcBuf);
}
} finally {
Shaderc.shaderc_compile_options_release(optionsHandle);
}
}
public static String preprocess(String source, int shaderKind, String debugName, Options opts) {
final String cleanSrc = source.indexOf('\0') >= 0 ? source.replace("\0", "") : source;
final long compiler = COMPILER.get();
final long optionsHandle = Shaderc.shaderc_compile_options_initialize();
if (optionsHandle == 0L) return null;
try {
configureOptions(optionsHandle, opts);
ByteBuffer srcBuf = null, nameBuf = null, entryBuf = null;
long result = 0L;
try {
srcBuf = memUTF8(cleanSrc, false);
nameBuf = memUTF8(debugName == null ? "shader" : debugName);
entryBuf = memUTF8("main");
result = Shaderc.shaderc_compile_into_preprocessed_text(compiler, srcBuf, shaderKind, nameBuf, entryBuf, optionsHandle);
if (result == 0L) return null;
if (Shaderc.shaderc_result_get_compilation_status(result) != Shaderc.shaderc_compilation_status_success) {
return null;
}
final ByteBuffer text = Shaderc.shaderc_result_get_bytes(result);
if (text == null || text.remaining() == 0) return null;
return MemoryUtil.memUTF8(text);
} finally {
if (result != 0L) Shaderc.shaderc_result_release(result);
if (entryBuf != null) memFree(entryBuf);
if (nameBuf != null) memFree(nameBuf);
if (srcBuf != null) memFree(srcBuf);
}
} finally {
Shaderc.shaderc_compile_options_release(optionsHandle);
}
}
private static void configureOptions(long h, Options o) {
Shaderc.shaderc_compile_options_set_target_env(h, o.targetEnv(), o.envVersion());
Shaderc.shaderc_compile_options_set_source_language(h, Shaderc.shaderc_source_language_glsl);
if (o.vulkanRulesRelaxed()) Shaderc.shaderc_compile_options_set_vulkan_rules_relaxed(h, true);
if (o.autoBindUniforms()) Shaderc.shaderc_compile_options_set_auto_bind_uniforms(h, true);
if (o.autoMapLocations()) Shaderc.shaderc_compile_options_set_auto_map_locations(h, true);
if (o.autoCombinedImageSampler()) Shaderc.shaderc_compile_options_set_auto_combined_image_sampler(h, true);
if (o.forcedVersion() != 0) {
Shaderc.shaderc_compile_options_set_forced_version_profile(h, o.forcedVersion(), o.forcedProfile());
}
}
public static void clearCache() {
synchronized (CACHE) { CACHE.clear(); }
}
private static @Nullable Path writeFailureDump(String debugName, int shaderKind, String msg, String source) {
try {
Files.createDirectories(FAILURE_DIR);
final int n = FAILURE_COUNTER.getAndIncrement();
final Path p = FAILURE_DIR.resolve(String.format("%03d_%s_kind0x%s.glsl", n, debugName == null ? "shader" : debugName, Integer.toHexString(shaderKind)));
final StringBuilder sb = new StringBuilder();
sb.append("// shaderc errors:\n");
if (msg != null) for (String line : msg.split("\\R")) sb.append("// ").append(line).append('\n');
sb.append('\n').append(source);
Files.writeString(p, sb.toString());
return p;
} catch (IOException e) {
LOGGER.warn("Failed to write shaderc failure dump for '{}': {}", debugName, e.getMessage());
return null;
}
}
}
@@ -0,0 +1,420 @@
package com.gtnewhorizons.angelica.glsm.shader;
import com.gtnewhorizons.angelica.glsm.GlslTransformUtils;
import com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.Edit;
import me.eigenraven.lwjgl3ify.api.Lwjgl3Aware;
import org.antlr.v4.runtime.ParserRuleContext;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import org.lwjgl.PointerBuffer;
import org.lwjgl.opengl.GL20;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL40;
import org.lwjgl.opengl.GL43;
import org.lwjgl.util.shaderc.Shaderc;
import org.lwjgl.util.spvc.Spvc;
import org.lwjgl.util.spvc.SpvcReflectedResource;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
import org.taumc.glsl.grammar.GLSLParser;
import org.taumc.glsl.grammar.GLSLParserBaseListener;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.startIdx;
import static com.gtnewhorizons.angelica.glsm.shader.GlslVulkanPreprocess.stopIdx;
import static org.lwjgl.system.MemoryStack.stackPush;
import static org.lwjgl.system.MemoryUtil.memFree;
/** GLSL 330 core -> SPIR-V -> GLSL ES 320 via shaderc + SPIRV-Cross */
@Lwjgl3Aware
public final class SpirvShaderTranslator {
private static final Logger LOGGER = LogManager.getLogger("SpirvShaderTranslator");
private static final int SPV_DECORATION_LOCATION = 30;
private static final int SPV_DECORATION_BINDING = 33;
private SpirvShaderTranslator() {}
public static @Nullable String glslToGlslEs(String source, int glShaderType, String debugName) {
return glslToGlslEsImpl(source, glShaderType, debugName, false);
}
static @Nullable String glslToGlslEsRawForTest(String source, int glShaderType, String debugName) {
return glslToGlslEsImpl(source, glShaderType, debugName, true);
}
private static @Nullable String glslToGlslEsImpl(String source, int glShaderType, String debugName, boolean rawOutput) {
final int shaderKind = shaderKindForGlType(glShaderType);
if (shaderKind < 0) {
LOGGER.warn("Unknown GL shader type 0x{} for '{}' - skipping SPIR-V translation", Integer.toHexString(glShaderType), debugName);
return null;
}
final GlslVulkanPreprocess.Result pre = GlslVulkanPreprocess.run(source, glShaderType, debugName, false);
if (pre == null) return null;
final SpirvCompiler.Result cr = SpirvCompiler.compile(pre.rewrittenSource(), shaderKind, debugName, SpirvCompiler.Options.vulkanRelaxed());
if (cr.spirv() == null) {
final String firstErr = cr.error() == null ? "(no error)" : cr.error().split("\\R", 2)[0];
LOGGER.error("shaderc failed for '{}' (kind=0x{}): {} (dumped to {})",
debugName, Integer.toHexString(shaderKind), firstErr, cr.dumpPath() == null ? "<dump failed>" : cr.dumpPath());
return null;
}
try {
final String emitted = emitGlslEs(cr.spirv(), debugName, glShaderType, pre.explicitVsInputs());
if (emitted == null) return null;
return rawOutput ? emitted : postProcessEsOutput(emitted, pre.boolUniforms());
} finally {
memFree(cr.spirv());
}
}
private static @Nullable String postProcessEsOutput(String glslEs, Set<String> boolUniformNames) {
final GLSLParser.Translation_unitContext root;
try {
root = GlslTransformUtils.parseFullQuiet(glslEs);
} catch (Exception e) {
LOGGER.error("Failed to parse spvc ES output for post-processing: {}", e.getMessage());
return null;
}
final DefaultBlockInfo block = findDefaultBlock(root);
final List<Edit> edits = new ArrayList<>();
if (block != null) {
edits.add(new Edit(startIdx(block.structExternalDecl), stopIdx(block.structExternalDecl), buildLooseUniforms(block.members, boolUniformNames)));
edits.add(new Edit(startIdx(block.uniformExternalDecl), stopIdx(block.uniformExternalDecl), ""));
}
final UseSiteRewriter rewriter = new UseSiteRewriter(block, boolUniformNames, edits);
ParseTreeWalker.DEFAULT.walk(rewriter, root);
return GlslVulkanPreprocess.applyEdits(glslEs, edits);
}
private record DefaultBlockInfo(ParserRuleContext structExternalDecl, ParserRuleContext uniformExternalDecl, String blockVarName, List<MemberInfo> members) {}
private record MemberInfo(String typePrefix, String name) {}
private static String buildLooseUniforms(List<MemberInfo> members, Set<String> boolNames) {
final StringBuilder sb = new StringBuilder();
for (MemberInfo m : members) {
if (boolNames.contains(m.name)) {
sb.append("uniform bool ").append(m.name).append(";\n");
} else {
sb.append("uniform ").append(m.typePrefix).append(' ').append(m.name).append(";\n");
}
}
if (!sb.isEmpty() && sb.charAt(sb.length() - 1) == '\n') sb.deleteCharAt(sb.length() - 1);
return sb.toString();
}
private static @Nullable DefaultBlockInfo findDefaultBlock(GLSLParser.Translation_unitContext root) {
final DefaultBlockFinder finder = new DefaultBlockFinder();
ParseTreeWalker.DEFAULT.walk(finder, root);
if (finder.structExternalDecl == null || finder.uniformExternalDecl == null) return null;
return new DefaultBlockInfo(finder.structExternalDecl, finder.uniformExternalDecl, finder.blockVarName, finder.members);
}
private static final class DefaultBlockFinder extends GLSLParserBaseListener {
String structName;
ParserRuleContext structExternalDecl;
ParserRuleContext uniformExternalDecl;
String blockVarName;
final List<MemberInfo> members = new ArrayList<>();
@Override
public void enterStruct_specifier(GLSLParser.Struct_specifierContext ctx) {
if (structExternalDecl != null) return;
final TerminalNode id = ctx.IDENTIFIER();
if (id == null) return;
final String idText = id.getText();
if (!idText.startsWith("_RESERVED_IDENTIFIER_FIXUP_") || !idText.endsWith("DefaultUniformBlock")) return;
structName = idText;
structExternalDecl = findExternalDeclaration(ctx);
for (GLSLParser.Struct_declarationContext sd : ctx.struct_declaration_list().struct_declaration()) {
final StringBuilder typePrefix = new StringBuilder();
if (sd.type_qualifier() != null) typePrefix.append(textWithSpaces(sd.type_qualifier())).append(' ');
typePrefix.append(textWithSpaces(sd.type_specifier()));
for (GLSLParser.Struct_declaratorContext decl : sd.struct_declarator_list().struct_declarator()) {
final String arraySuffix = decl.array_specifier() == null ? "" : decl.array_specifier().getText();
members.add(new MemberInfo(typePrefix + arraySuffix, decl.IDENTIFIER().getText()));
}
}
}
@Override
public void enterInit_declarator_list(GLSLParser.Init_declarator_listContext ctx) {
if (structName == null || uniformExternalDecl != null) return;
final GLSLParser.Single_declarationContext single = ctx.single_declaration();
if (single == null || single.typeless_declaration() == null) return;
final GLSLParser.Fully_specified_typeContext fst = single.fully_specified_type();
if (fst == null || fst.type_qualifier() == null) return;
boolean isUniform = false;
for (GLSLParser.Single_type_qualifierContext stq : fst.type_qualifier().single_type_qualifier()) {
if (stq.storage_qualifier() != null && "uniform".equals(stq.storage_qualifier().getText())) {
isUniform = true;
break;
}
}
if (!isUniform) return;
if (fst.type_specifier() == null || fst.type_specifier().type_specifier_nonarray() == null) return;
final GLSLParser.Type_specifier_nonarrayContext ts = fst.type_specifier().type_specifier_nonarray();
if (ts.type_name() == null) return;
if (!structName.equals(ts.type_name().getText())) return;
blockVarName = single.typeless_declaration().IDENTIFIER().getText();
uniformExternalDecl = findExternalDeclaration(ctx);
}
private static String textWithSpaces(ParseTree tree) {
final StringBuilder sb = new StringBuilder();
appendTextWithSpaces(tree, sb);
return sb.toString().trim();
}
private static void appendTextWithSpaces(ParseTree tree, StringBuilder sb) {
if (tree instanceof TerminalNode) {
if (!sb.isEmpty() && sb.charAt(sb.length() - 1) != ' ') sb.append(' ');
sb.append(tree.getText());
return;
}
for (int i = 0; i < tree.getChildCount(); i++) {
appendTextWithSpaces(tree.getChild(i), sb);
}
}
}
private static ParserRuleContext findExternalDeclaration(ParserRuleContext ctx) {
ParseTree cur = ctx;
while (cur != null && !(cur instanceof GLSLParser.External_declarationContext)) {
cur = cur.getParent();
}
return (ParserRuleContext) cur;
}
private static final class UseSiteRewriter extends GLSLParserBaseListener {
private final @Nullable DefaultBlockInfo block;
private final Set<String> boolNames;
private final List<Edit> edits;
private final Set<String> memberNames;
UseSiteRewriter(@Nullable DefaultBlockInfo block, Set<String> boolNames, List<Edit> edits) {
this.block = block;
this.boolNames = boolNames;
this.edits = edits;
this.memberNames = new HashSet<>();
if (block != null) for (MemberInfo m : block.members) memberNames.add(m.name);
}
@Override
public void enterBinary_expression(GLSLParser.Binary_expressionContext ctx) {
if (ctx.getChildCount() != 3) return;
final ParseTree opNode = ctx.getChild(1);
if (!(opNode instanceof TerminalNode op)) return;
final String opText = op.getText();
final boolean isNe = "!=".equals(opText);
final boolean isEq = "==".equals(opText);
if (!isNe && !isEq) return;
final ParseTree rhs = ctx.getChild(2);
if (!"0u".equals(rhs.getText())) return;
final ParseTree lhs = ctx.getChild(0);
final String nameIfBool = resolveBoolOperand(lhs);
if (nameIfBool == null) return;
final String replacement = isNe ? nameIfBool : "!" + nameIfBool;
edits.add(new Edit(startIdx(ctx), stopIdx(ctx), replacement));
}
@Override
public void enterPostfix_expression(GLSLParser.Postfix_expressionContext ctx) {
if (ctx.type_specifier() != null && ctx.LEFT_PAREN() != null
&& "bool".equals(ctx.type_specifier().getText())
&& ctx.function_call_parameters() != null
&& ctx.function_call_parameters().assignment_expression() != null
&& ctx.function_call_parameters().assignment_expression().size() == 1) {
final String nameIfBool = resolveBoolOperand(ctx.function_call_parameters().assignment_expression(0));
if (nameIfBool != null) {
edits.add(new Edit(startIdx(ctx), stopIdx(ctx), nameIfBool));
return;
}
}
if (block != null && ctx.getChildCount() == 3 && ctx.DOT() != null && ctx.field_selection() != null) {
final ParseTree inner = ctx.getChild(0);
if (inner.getText().equals(block.blockVarName)) {
final String member = ctx.field_selection().getText();
if (memberNames.contains(member) && !coveredByAncestorBoolCompare(ctx)) {
edits.add(new Edit(startIdx(ctx), stopIdx(ctx), member));
}
}
}
}
private @Nullable String resolveBoolOperand(ParseTree operand) {
final String text = operand.getText();
if (boolNames.contains(text)) return text;
if (block != null && text.startsWith(block.blockVarName + ".")) {
final String member = text.substring(block.blockVarName.length() + 1);
if (boolNames.contains(member) && memberNames.contains(member)) return member;
}
return null;
}
private boolean coveredByAncestorBoolCompare(GLSLParser.Postfix_expressionContext ctx) {
final int start = startIdx(ctx), stop = stopIdx(ctx);
for (Edit e : edits) {
if (e.startIdx() <= start && stop <= e.stopIdx() && e.stopIdx() > stop) return true;
}
return false;
}
}
private static @Nullable String emitGlslEs(ByteBuffer spirv, String debugName, int glShaderType, Set<String> explicitVsInputs) {
try (MemoryStack stack = stackPush()) {
final PointerBuffer pContext = stack.pointers(0);
if (Spvc.spvc_context_create(pContext) != Spvc.SPVC_SUCCESS) {
LOGGER.error("spvc_context_create failed for '{}'", debugName);
return null;
}
final long ctx = pContext.get(0);
try {
final IntBuffer spirvWords = spirv.asIntBuffer();
final PointerBuffer pIr = stack.pointers(0);
if (Spvc.spvc_context_parse_spirv(ctx, spirvWords, spirvWords.remaining(), pIr) != Spvc.SPVC_SUCCESS) {
LOGGER.error("spvc_context_parse_spirv failed for '{}': {}", debugName, lastSpvcError(ctx));
return null;
}
final PointerBuffer pCompiler = stack.pointers(0);
if (Spvc.spvc_context_create_compiler(ctx, Spvc.SPVC_BACKEND_GLSL, pIr.get(0), Spvc.SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, pCompiler) != Spvc.SPVC_SUCCESS) {
LOGGER.error("spvc_context_create_compiler(GLSL) failed for '{}': {}", debugName, lastSpvcError(ctx));
return null;
}
final long compiler = pCompiler.get(0);
final PointerBuffer pOptions = stack.pointers(0);
if (Spvc.spvc_compiler_create_compiler_options(compiler, pOptions) != Spvc.SPVC_SUCCESS) {
LOGGER.error("spvc_compiler_create_compiler_options failed for '{}': {}", debugName, lastSpvcError(ctx));
return null;
}
final long options = pOptions.get(0);
Spvc.spvc_compiler_options_set_uint(options, Spvc.SPVC_COMPILER_OPTION_GLSL_VERSION, 320);
Spvc.spvc_compiler_options_set_bool(options, Spvc.SPVC_COMPILER_OPTION_GLSL_ES, true);
Spvc.spvc_compiler_options_set_bool(options, Spvc.SPVC_COMPILER_OPTION_GLSL_EMIT_UNIFORM_BUFFER_AS_PLAIN_UNIFORMS, true);
if (Spvc.spvc_compiler_install_compiler_options(compiler, options) != Spvc.SPVC_SUCCESS) {
LOGGER.error("spvc_compiler_install_compiler_options failed for '{}': {}", debugName, lastSpvcError(ctx));
return null;
}
applyResourceDecorationFixups(ctx, compiler, glShaderType, explicitVsInputs, debugName);
final PointerBuffer pSource = stack.pointers(0);
if (Spvc.spvc_compiler_compile(compiler, pSource) != Spvc.SPVC_SUCCESS) {
LOGGER.error("spvc_compiler_compile failed for '{}': {}", debugName, lastSpvcError(ctx));
return null;
}
final long sourcePtr = pSource.get(0);
if (sourcePtr == 0L) {
LOGGER.error("spvc_compiler_compile returned NULL source for '{}'", debugName);
return null;
}
return MemoryUtil.memUTF8(sourcePtr);
} finally {
Spvc.spvc_context_destroy(ctx);
}
}
}
private static void applyResourceDecorationFixups(long ctx, long compiler, int glShaderType, Set<String> explicitVsInputs, String debugName) {
if (glShaderType == GL20.GL_VERTEX_SHADER) {
unsetDecorationForResourceType(ctx, compiler, Spvc.SPVC_RESOURCE_TYPE_STAGE_OUTPUT, SPV_DECORATION_LOCATION, null, debugName);
unsetDecorationForResourceType(ctx, compiler, Spvc.SPVC_RESOURCE_TYPE_STAGE_INPUT, SPV_DECORATION_LOCATION, explicitVsInputs, debugName);
} else if (glShaderType == GL20.GL_FRAGMENT_SHADER) {
unsetDecorationForResourceType(ctx, compiler, Spvc.SPVC_RESOURCE_TYPE_STAGE_INPUT, SPV_DECORATION_LOCATION, null, debugName);
} else if (glShaderType == GL32.GL_GEOMETRY_SHADER || glShaderType == GL40.GL_TESS_CONTROL_SHADER || glShaderType == GL40.GL_TESS_EVALUATION_SHADER) {
unsetDecorationForResourceType(ctx, compiler, Spvc.SPVC_RESOURCE_TYPE_STAGE_OUTPUT, SPV_DECORATION_LOCATION, null, debugName);
unsetDecorationForResourceType(ctx, compiler, Spvc.SPVC_RESOURCE_TYPE_STAGE_INPUT, SPV_DECORATION_LOCATION, null, debugName);
}
if (glShaderType != GL43.GL_COMPUTE_SHADER) {
for (int resourceType : BINDING_STRIP_RESOURCE_TYPES) {
unsetDecorationForResourceType(ctx, compiler, resourceType, SPV_DECORATION_BINDING, null, debugName);
}
}
}
private static final int[] BINDING_STRIP_RESOURCE_TYPES = {
Spvc.SPVC_RESOURCE_TYPE_UNIFORM_BUFFER,
Spvc.SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
Spvc.SPVC_RESOURCE_TYPE_SEPARATE_IMAGE,
Spvc.SPVC_RESOURCE_TYPE_SEPARATE_SAMPLERS,
Spvc.SPVC_RESOURCE_TYPE_STORAGE_IMAGE,
};
private static void unsetDecorationForResourceType(long ctx, long compiler, int resourceType, int decoration, Set<String> preserve, String debugName) {
try (MemoryStack stack = stackPush()) {
final PointerBuffer pResources = stack.pointers(0);
if (Spvc.spvc_compiler_create_shader_resources(compiler, pResources) != Spvc.SPVC_SUCCESS) {
LOGGER.warn("spvc_compiler_create_shader_resources failed for '{}': {}", debugName, lastSpvcError(ctx));
return;
}
final long resources = pResources.get(0);
final PointerBuffer pList = stack.pointers(0);
final PointerBuffer pCount = stack.pointers(0);
if (Spvc.spvc_resources_get_resource_list_for_type(resources, resourceType, pList, pCount) != Spvc.SPVC_SUCCESS) {
LOGGER.warn("spvc_resources_get_resource_list_for_type failed for '{}': {}", debugName, lastSpvcError(ctx));
return;
}
final long listAddr = pList.get(0);
final long count = pCount.get(0);
if (listAddr == 0L || count <= 0L) return;
final SpvcReflectedResource.Buffer list = SpvcReflectedResource.create(listAddr, (int) count);
for (int i = 0; i < list.remaining(); i++) {
final SpvcReflectedResource res = list.get(i);
if (preserve != null) {
final String name = res.nameString();
if (name != null && preserve.contains(name)) continue;
}
Spvc.spvc_compiler_unset_decoration(compiler, res.id(), decoration);
}
}
}
private static String lastSpvcError(long ctx) {
final String msg = Spvc.spvc_context_get_last_error_string(ctx);
return msg == null || msg.isEmpty() ? "(no error message)" : msg;
}
private static int shaderKindForGlType(int glShaderType) {
return switch (glShaderType) {
case GL20.GL_VERTEX_SHADER -> Shaderc.shaderc_vertex_shader;
case GL20.GL_FRAGMENT_SHADER -> Shaderc.shaderc_fragment_shader;
case GL32.GL_GEOMETRY_SHADER -> Shaderc.shaderc_geometry_shader;
case GL40.GL_TESS_CONTROL_SHADER -> Shaderc.shaderc_tess_control_shader;
case GL40.GL_TESS_EVALUATION_SHADER -> Shaderc.shaderc_tess_evaluation_shader;
case GL43.GL_COMPUTE_SHADER -> Shaderc.shaderc_compute_shader;
default -> -1;
};
}
}
@@ -1,4 +1,4 @@
package net.coderbot.iris.gl.uniform;
package com.gtnewhorizons.angelica.glsm.shader;
public enum UniformType {
INT,
Loaded 100 of 1228 files, more files were not shown because too many files have changed in this diff. Show more