diff --git a/.efrocachemap b/.efrocachemap
index 63a65228..4809a7f7 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -421,42 +421,42 @@
"build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26",
"build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8",
"build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55",
- "build/assets/ba_data/data/langdata.json": "44b7cb7d2ce62346834ab48d0d1e81bc",
- "build/assets/ba_data/data/languages/arabic.json": "db961f7fe0541a31880929e1c17ea957",
+ "build/assets/ba_data/data/langdata.json": "ffaf99c11311bb311421f8537be1fab9",
+ "build/assets/ba_data/data/languages/arabic.json": "c246699b9c1949ce7542d32d30d47632",
"build/assets/ba_data/data/languages/belarussian.json": "995ee0abd5bc05704e9f5a7712774663",
- "build/assets/ba_data/data/languages/chinese.json": "8fc810e920164f3d7374aaab8000e9ae",
- "build/assets/ba_data/data/languages/chinesetraditional.json": "3fe960a8f0ca529aa57b4f9cb7385abc",
+ "build/assets/ba_data/data/languages/chinese.json": "8d889accdd49334591209bdaf6eaf02f",
+ "build/assets/ba_data/data/languages/chinesetraditional.json": "19be7dcc11f5a9ed4fc408a0216ab36b",
"build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa",
- "build/assets/ba_data/data/languages/czech.json": "f3ce219840946cb8f9aa6d3e25927ab3",
+ "build/assets/ba_data/data/languages/czech.json": "70992c2e2ac08a1f95a3e94318ab3332",
"build/assets/ba_data/data/languages/danish.json": "3fd69080783d5c9dcc0af737f02b6f1e",
"build/assets/ba_data/data/languages/dutch.json": "22b44a33bf81142ba2befad14eb5746e",
- "build/assets/ba_data/data/languages/english.json": "6d261a19b40a27eca92f6199a26f5779",
+ "build/assets/ba_data/data/languages/english.json": "b38d54aecf3ac47b8d8ca97d8bab3006",
"build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880",
"build/assets/ba_data/data/languages/filipino.json": "58f363cfd8a3ccf0c904ab753d95789b",
- "build/assets/ba_data/data/languages/french.json": "6057b18878ad8379e51b507fa94958d8",
+ "build/assets/ba_data/data/languages/french.json": "d8527da977a563185de25ef02bacf826",
"build/assets/ba_data/data/languages/german.json": "549754d2a530d825200c6126be56df5c",
- "build/assets/ba_data/data/languages/gibberish.json": "d23fe0936bb6177443f4b74bf5981b13",
+ "build/assets/ba_data/data/languages/gibberish.json": "837423db378b3e7679683805826aa26e",
"build/assets/ba_data/data/languages/greek.json": "a65d78f912e9a89f98de004405167a6a",
"build/assets/ba_data/data/languages/hindi.json": "88ee0cda537bab9ac827def5e236fe1a",
"build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e",
"build/assets/ba_data/data/languages/indonesian.json": "00b351a98d6fc301df604e1e9d56a055",
- "build/assets/ba_data/data/languages/italian.json": "11f0a95abce8ef7b667b0d557746ecbe",
+ "build/assets/ba_data/data/languages/italian.json": "338e7a03dff47f4eefc0ca3a995cd4f4",
"build/assets/ba_data/data/languages/korean.json": "ca1122a9ee551da3f75ae632012bd0e2",
"build/assets/ba_data/data/languages/malay.json": "832562ce997fc70704b9234c95fb2e38",
- "build/assets/ba_data/data/languages/persian.json": "0cf93f27181dd3ef4b0d03b88bf899cf",
+ "build/assets/ba_data/data/languages/persian.json": "71cc5b33abda0f285b970b8cc4a014a8",
"build/assets/ba_data/data/languages/polish.json": "826c5b0402c2f0bcc29bc6f48b833545",
"build/assets/ba_data/data/languages/portuguese.json": "99b27c598c90fd522132af3536aef0ee",
"build/assets/ba_data/data/languages/romanian.json": "aeebdd54f65939c2facc6ac50c117826",
- "build/assets/ba_data/data/languages/russian.json": "75ee4f36356f4f8a8413d4e0b6f5e268",
+ "build/assets/ba_data/data/languages/russian.json": "910cf653497654a16d5c4f067d6def22",
"build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69",
"build/assets/ba_data/data/languages/slovak.json": "27962d53dc3f7dd4e877cd40faafeeef",
- "build/assets/ba_data/data/languages/spanish.json": "b59d7ad1a98831afbc4ab162af08ec51",
+ "build/assets/ba_data/data/languages/spanish.json": "0122b0b24aa111ab259af02bbae9b7b6",
"build/assets/ba_data/data/languages/swedish.json": "77d671f10613291ebf9c71da66f18a18",
"build/assets/ba_data/data/languages/tamil.json": "b9d4b4e107456ea6420ee0f9d9d7a03e",
"build/assets/ba_data/data/languages/thai.json": "33f63753c9af9a5b238d229a0bf23fbc",
- "build/assets/ba_data/data/languages/turkish.json": "a5347d5f7fc9dc994053001a936964a4",
+ "build/assets/ba_data/data/languages/turkish.json": "9d7e58c9062dc517c3779c255a9b3142",
"build/assets/ba_data/data/languages/ukrainian.json": "f72eb51abfbbb56e27866895d7e947d2",
- "build/assets/ba_data/data/languages/venetian.json": "b0e3d73ccf96c5fa490a54f090ee77a5",
+ "build/assets/ba_data/data/languages/venetian.json": "71ff7ec07a1f9715fea93229bff249e1",
"build/assets/ba_data/data/languages/vietnamese.json": "921cd1e50f60fe3e101f246e172750ba",
"build/assets/ba_data/data/maps/big_g.json": "1dd301d490643088a435ce75df971054",
"build/assets/ba_data/data/maps/bridgit.json": "6aea74805f4880cc11237c5734a24422",
@@ -1416,10 +1416,10 @@
"build/assets/ba_data/textures/crossOutMask.pvr": "94110cc4e3e47f81b68f548951a33c2b",
"build/assets/ba_data/textures/crossOutMask_preview.png": "d5df4d494cfbf700e3c8726b3693716c",
"build/assets/ba_data/textures/crossOut_preview.png": "a0628f1e6b7e9f7d3b73d1c835ec9286",
- "build/assets/ba_data/textures/cursor.dds": "68e40aed124670a2b53c78101a8b3889",
- "build/assets/ba_data/textures/cursor.ktx": "627cbabe2eceff9992d31d4a48e98966",
- "build/assets/ba_data/textures/cursor.pvr": "704963da7a62d555e1dd907076033a02",
- "build/assets/ba_data/textures/cursor_preview.png": "8d80f75bcdc7f50b479d7756e68b8629",
+ "build/assets/ba_data/textures/cursor.dds": "575b05e3adc74adf5a5d4b482a54adc9",
+ "build/assets/ba_data/textures/cursor.ktx": "56ef6481222c23cbc1ab0fe825f19b03",
+ "build/assets/ba_data/textures/cursor.pvr": "344b8856a315af23f495ebd283ee54fa",
+ "build/assets/ba_data/textures/cursor_preview.png": "0f6820abfe6b79b4133971ace8f3bc42",
"build/assets/ba_data/textures/cuteSpaz.dds": "5876162f89e558a2220935a1d63493c3",
"build/assets/ba_data/textures/cuteSpaz.ktx": "4a3bc3c1739991298d21a66256289d57",
"build/assets/ba_data/textures/cuteSpaz.pvr": "a236803464dc49b61b63a5e83d305c4c",
@@ -1520,10 +1520,10 @@
"build/assets/ba_data/textures/folder.ktx": "293ec1bc118dd368e41623d5341e4428",
"build/assets/ba_data/textures/folder.pvr": "22c439c211b592f41987b865d770bc77",
"build/assets/ba_data/textures/folder_preview.png": "6e4892d7d43289bc22c8ff94c7c15955",
- "build/assets/ba_data/textures/fontBig.dds": "962a8dce3f3fdeebbb8abcc0682bcb74",
- "build/assets/ba_data/textures/fontBig.ktx": "3b999da15c56a6f38bd7cdff5632af9d",
- "build/assets/ba_data/textures/fontBig.pvr": "3d931bdabdc5097b37be763594b28678",
- "build/assets/ba_data/textures/fontBig_preview.png": "540b6bca8f5050a8ed246dce542aef93",
+ "build/assets/ba_data/textures/fontBig.dds": "4242bbb85bc1ebd1ed63d7660587bd3c",
+ "build/assets/ba_data/textures/fontBig.ktx": "94b56c2488d6c9ebabfbbb740eca07dd",
+ "build/assets/ba_data/textures/fontBig.pvr": "dff3f6c04a8c7b0bb937001640b42c8d",
+ "build/assets/ba_data/textures/fontBig_preview.png": "f8b15cb04f0deca7774def335a72f053",
"build/assets/ba_data/textures/fontExtras.dds": "7ab11df1b3a3daa651dfad34219b89f5",
"build/assets/ba_data/textures/fontExtras.ktx": "30c3c8ca2cdf1209ff177017bb10f0a8",
"build/assets/ba_data/textures/fontExtras.pvr": "fd3b0bd902c30e4b7aa5fe00e1eec4be",
@@ -4042,7 +4042,7 @@
"build/assets/windows/Win32/Lib/zoneinfo/_tzpath.py": "08a2e9502e68a3e35c45067f7439b108",
"build/assets/windows/Win32/Lib/zoneinfo/_zoneinfo.py": "88e71c4db229ce21321b991cd7162d23",
"build/assets/windows/Win32/OpenAL32.dll": "8bcdadebd8bc95a591e727a04faebda8",
- "build/assets/windows/Win32/SDL2.dll": "e358fddbc36ebf4dc8aba79a99466747",
+ "build/assets/windows/Win32/SDL2.dll": "3937d3151ffb6544bf9657d0e45a8d0a",
"build/assets/windows/Win32/libvorbis.dll": "dcfa5c5534900b7c109adc820ae57d74",
"build/assets/windows/Win32/libvorbisfile.dll": "8abd2d7131857b9cc689b2857d04d018",
"build/assets/windows/Win32/msvcp140d.dll": "d172cacd2087cf34db2fb8bb3c29c337",
@@ -4056,50 +4056,50 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
- "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "e7171e1db64d2c0fdebbaeb45d99422e",
- "build/prefab/full/linux_arm64_gui/release/ballisticakit": "4f6ed2673aa7716c211c94baeb611da3",
- "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "629d63c3627b863e817a98b891b38325",
- "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "b6315ff0d68b436500e48de435b69c77",
- "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "aa98cf408c857767bcbb41ce2b63ce76",
- "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "fa56d6e2beb16a44eff88dd9ca3e0b3e",
- "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "3f026a96404b41c4369bd40694eae4de",
- "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "9713455275e8d7b6bc98fa85fbc1e3ee",
- "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "051d1a2636fe1b5dadb1f9806b9040a3",
- "build/prefab/full/mac_arm64_gui/release/ballisticakit": "ff117aec63d1413c9f48bac4fd20c9a5",
- "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "0502bb185cf85e224628b3be5e18750a",
- "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "8999ff0a5601c423e2fa10b59425bfa2",
- "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "f2d0d08db00ee44a5c98023ea8521bcc",
- "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "35e1d1017b406d9ed31b2b901a1d0dbe",
- "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "6346c88ec18632ac3ad63beae6122569",
- "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "a08613797c5064a0b2302b9468dc88e9",
- "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "f3d4f9dc7162d7f736c32f8a8ead5477",
- "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "dbddd278a058fb61b8ca5301a3ae4a09",
- "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "aca30445a8fc28b69a2bce57bd651dc3",
- "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "1ba63aa89b4b45d893499a491e3047cd",
- "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "1e786451b0abe1451f17b908c2d8abb3",
- "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "80db3e75458db18efe1657f8ce686996",
- "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "1e786451b0abe1451f17b908c2d8abb3",
- "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "80db3e75458db18efe1657f8ce686996",
- "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "a29bc1b96b2422dce9154ef8a404a8e6",
- "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "c36dc72d78f9df240ae9f640dee470c2",
- "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "a29bc1b96b2422dce9154ef8a404a8e6",
- "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "c36dc72d78f9df240ae9f640dee470c2",
- "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "d1e2aef8e1ba4fac62908efd3b8079c4",
- "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "4dd471e8559a31c1425cd344646261b5",
- "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "d1e2aef8e1ba4fac62908efd3b8079c4",
- "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "4dd471e8559a31c1425cd344646261b5",
- "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "0bff76811b9640d20c7104b8dabf27e8",
- "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "b6e72c87d43dbf2a93e9a0b74952677b",
- "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "3c8593e81564012a7638a20c0dc0267b",
- "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "b6e72c87d43dbf2a93e9a0b74952677b",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "a6b742a577b1a2a5b99ef4acc4949735",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "3019670a3df31b6ed2bbd5d3a847afbb",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "85335e293ae1433afdf40a83e346d379",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "172855f5f89e9bd36efe7bacad3d5fc9",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "6fa16da3c74bc305a550697b7b453b21",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "dc2d4d3655e8cd65d83e355e731441a8",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "24a6884294c6c6da095e24f0dfa0faf4",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "68f39e024d3ca6bef9a0dc593ba139ab",
+ "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "24a88ffc78cd3f59a7fa7df974d79e25",
+ "build/prefab/full/linux_arm64_gui/release/ballisticakit": "ba97e6d831cfc129ebc46443b49c3055",
+ "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "f054f613e6a053a281b3d75c0bc19a8b",
+ "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "c95435393ac3946b0cdd288cea6fd578",
+ "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "a0ef35af73a0d6566f57cd57c603c3cd",
+ "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "ef54d4c6043e4fd789936d5fc60757af",
+ "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "a2dc425aa51d6fd086346e9418d21b97",
+ "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "73ba9c896b534902308178627e67746b",
+ "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "bc6e2a93f1cb4cf0b19ceb577eb15ab9",
+ "build/prefab/full/mac_arm64_gui/release/ballisticakit": "4e813fd82f679f2d87d420535cd0d549",
+ "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "24497a56d078c29c8ebaca01b655dc7c",
+ "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "955dd7032ecd7fb0e329a40964cdebb3",
+ "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "cb0519ef32e0e5a2cdcb0405def7bd9c",
+ "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "df6c673f248be2612388edc56b1e5288",
+ "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "a4913759d8f9b6013fae9c213441298d",
+ "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "1f4534097a3b1bfa1f26aec4e7eefff3",
+ "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "c97a6cc985522514c5ba5b57cd4a6b70",
+ "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "4cd0b0cd2f114ef58dc25cf148e3be64",
+ "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "4beca89d3ef221cf195bef82933a5e96",
+ "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "6d97073ef999fe7e44fa9aa8ef1b5a42",
+ "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "f3d305e647a7f77dd70a48f615cfd750",
+ "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "931ce8eab9859d20ad86c47d196ba62c",
+ "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "f3d305e647a7f77dd70a48f615cfd750",
+ "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "931ce8eab9859d20ad86c47d196ba62c",
+ "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "e80b5f536b30a23fe107852a0c2f7536",
+ "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "076df48013d64bc07aa59001819f8583",
+ "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "e80b5f536b30a23fe107852a0c2f7536",
+ "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "076df48013d64bc07aa59001819f8583",
+ "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "fcfdeb63ced9156995cf1b08ae5c861f",
+ "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "d82ad28301dc8e5b0ca98cf1da5d907c",
+ "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "fcfdeb63ced9156995cf1b08ae5c861f",
+ "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "d82ad28301dc8e5b0ca98cf1da5d907c",
+ "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "6db0247bf985f9d8c7ed85a5e5508a8b",
+ "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "097e17c460bf798edf61303789860596",
+ "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "14df40bc07bdde8184843d16d5ba7798",
+ "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "097e17c460bf798edf61303789860596",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "8542bbc154faedf633dd82bc78dc50b7",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "eb625ccd56cd37e26b6abb99ecc385c3",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "835d9280dddb9fa6554e631184a62f6d",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "5aca1f8dfd9ff294cf2ec2229e393d8e",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "f270764cfbb3791752fbd265b5787889",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "64ca7172f66176fef0ef71d5b1e51663",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "81b8aeeecb650f57d4f89636cbdf5916",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "a3c06c787f387c586de3ff6b092c32dc",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "f8cd3af311ac63147882590123b78318",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "c81b2b1f3a14b4cd20a7b93416fe893a",
diff --git a/.gitignore b/.gitignore
index f8268232..07de7ab4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -62,7 +62,6 @@ libs/
# Visual Studio
.vs
-*.vcxproj.user
*.aps
*.ncb
*.opendb
diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index bfdb3300..56878ffb 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -1975,6 +1975,7 @@
noninteractively
nonmultipart
noone
+ nopull
norun
nospeak
nosub
@@ -2447,6 +2448,7 @@
rawkey
rawpath
rawpaths
+ rawval
rayd
rcade
rcfile
@@ -2821,6 +2823,7 @@
stdobj
stdsettings
stdspaz
+ steamdeck
stedit
steelseries
stgdict
@@ -3318,6 +3321,7 @@
writeclasses
writefuncs
wslpath
+ wslview
wspath
wsroot
wtcolor
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92d36bf8..76a542c2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,29 @@
-### 1.7.28 (build 21342, api 8, 2023-09-13)
+### 1.7.28 (build 21385, api 8, 2023-09-27)
+- Massively cleaned up code related to rendering and window systems (OpenGL,
+ SDL, etc). This code had been growing into a nasty tangle for 15 years
+ attempting to support various old/hacked versions of SDL, etc. I ripped out
+ huge chunks of it and put back still-relevant pieces in a much more cleanly
+ designed way. This should put us in a much better place for supporting various
+ platforms and making graphical improvements going forward. See
+ `ballistica/base/app_adapter/app_adapter_sdl.cc` for an example of the now
+ nicely implemented system.
+- The engine now requires OpenGL 3.0 or newer on desktop and OpenGL ES 3.0 or
+ newer on mobile. This means we're cutting off a few percent of old devices on
+ Android that only support ES 2, but ES 3 has been out for 10 years now so I
+ feel it is time. As mentioned above, this allows massively cleaning up the
+ graphics code which means we can start to improve it.
+- Removed gamma controls. These were only active on the old Mac version anyway
+ and are being removed from the upcoming SDL3, so if we want this sort of thing
+ we should do it through shading in the renderer now.
+- Implemented both vsync and max-fps for the SDL build of the game. This means
+ you can finally take advantage of that nice high frame rate monitor on your
+ PC. Vsync supports 'Disable', 'Enabled' and 'Auto', which attempts to use
+ 'adaptive' vsync if available, and no vsync otherwise.
+- Spent some time tuning a few frame-timing mechanisms, so motion in the game
+ should appear significantly smoother in some cases. Please let me know if it
+ ever appears *less* smooth than before or if you see what looks like weird
+ speed changes which could be timing problems.
- Renamed Console to DevConsole, and added an option under advanced settings to
always show a 'dev' button onscreen which can be used to toggle it. The
backtick key still works also for anyone with a keyboard. I plan to add more
@@ -42,6 +66,17 @@
to fail in some builds/runs (thanks Rikko for the heads-up).
- (build 21327) Fixed an issue that could cause the app to pause for 3 seconds
at shutdown.
+- Worked to improve sanity checking on C++ RenderComponents in debug builds to
+ make it easier to use and avoid sending broken commands to the renderer. Some
+ specifics follow.
+- RenderComponents no longer need an explicit Submit() at the end; if one goes
+ out of scope not in the submitted state it will implicitly run a submit.
+ Hopefully this will encourage concise code where RenderComponents are defined
+ in tight scopes.
+- RenderComponents now have a ScopedTransform() call which can be used to push
+ and pop the transform stack based on C++ scoping instead of the old
+ PushTransform/PopTransform. This should make it harder to accidentally break
+ the transform stack with unbalanced components.
### 1.7.27 (build 21282, api 8, 2023-08-30)
diff --git a/Makefile b/Makefile
index 8b42f212..a3c6c68e 100644
--- a/Makefile
+++ b/Makefile
@@ -955,8 +955,7 @@ WINDOWS_CONFIGURATION ?= Debug
# Stage assets and other files so a built binary will run.
windows-staging: assets-windows resources meta
- $(STAGE_BUILD) -win-$(WINPLT) -$(WINCFGLC) \
- build/windows/$(WINCFG)_$(WINPLT)
+ $(STAGE_BUILD) -win-$(WINPLT) -$(WINCFGLC) build/windows/$(WINCFG)_$(WINPLT)
# Build and run a debug windows build (from WSL).
windows-debug: windows-debug-build
diff --git a/ballisticakit-cmake/.idea/dictionaries/ericf.xml b/ballisticakit-cmake/.idea/dictionaries/ericf.xml
index 8bf0d316..661c382f 100644
--- a/ballisticakit-cmake/.idea/dictionaries/ericf.xml
+++ b/ballisticakit-cmake/.idea/dictionaries/ericf.xml
@@ -1176,6 +1176,7 @@
noninteractively
nonlint
noone
+ nopull
notarytool
nothin
notorize
@@ -1446,6 +1447,7 @@
raspbian
rasterizer
rawkey
+ rawval
rayd
rcade
rcva
@@ -1675,6 +1677,7 @@
staticdata
statictest
stdint
+ steamdeck
stepfast
stephane
stepnum
@@ -1950,6 +1953,7 @@
wreadlink
writeauxiliaryfile
writecall
+ wslview
wspath
wsroot
wtfslice
diff --git a/ballisticakit-cmake/CMakeLists.txt b/ballisticakit-cmake/CMakeLists.txt
index b9892c57..cd0abc3d 100644
--- a/ballisticakit-cmake/CMakeLists.txt
+++ b/ballisticakit-cmake/CMakeLists.txt
@@ -205,6 +205,8 @@ set(BALLISTICA_SOURCES
# AUTOGENERATED_PUBLIC_BEGIN (this section is managed by the "update_project" tool)
${BA_SRC_ROOT}/ballistica/base/app_adapter/app_adapter.cc
${BA_SRC_ROOT}/ballistica/base/app_adapter/app_adapter.h
+ ${BA_SRC_ROOT}/ballistica/base/app_adapter/app_adapter_apple.cc
+ ${BA_SRC_ROOT}/ballistica/base/app_adapter/app_adapter_apple.h
${BA_SRC_ROOT}/ballistica/base/app_adapter/app_adapter_headless.cc
${BA_SRC_ROOT}/ballistica/base/app_adapter/app_adapter_headless.h
${BA_SRC_ROOT}/ballistica/base/app_adapter/app_adapter_sdl.cc
@@ -284,10 +286,31 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/base/graphics/component/special_component.h
${BA_SRC_ROOT}/ballistica/base/graphics/component/sprite_component.cc
${BA_SRC_ROOT}/ballistica/base/graphics/component/sprite_component.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/framebuffer_object_gl.h
${BA_SRC_ROOT}/ballistica/base/graphics/gl/gl_sys.cc
${BA_SRC_ROOT}/ballistica/base/graphics/gl/gl_sys.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/gl_sys_windows.cc
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/gl_sys_windows.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/mesh/mesh_asset_data_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/mesh/mesh_data_dual_texture_full_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/mesh/mesh_data_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/mesh/mesh_data_object_split_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/mesh/mesh_data_simple_full_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/mesh/mesh_data_simple_split_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/mesh/mesh_data_smoke_full_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/mesh/mesh_data_sprite_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/program/program_blur_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/program/program_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/program/program_object_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/program/program_post_process_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/program/program_shield_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/program/program_simple_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/program/program_smoke_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/program/program_sprite_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/render_target_gl.h
${BA_SRC_ROOT}/ballistica/base/graphics/gl/renderer_gl.cc
${BA_SRC_ROOT}/ballistica/base/graphics/gl/renderer_gl.h
+ ${BA_SRC_ROOT}/ballistica/base/graphics/gl/texture_data_gl.h
${BA_SRC_ROOT}/ballistica/base/graphics/graphics.cc
${BA_SRC_ROOT}/ballistica/base/graphics/graphics.h
${BA_SRC_ROOT}/ballistica/base/graphics/graphics_server.cc
diff --git a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj
index c0352e10..ed6cfdcc 100644
--- a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj
+++ b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj
@@ -191,6 +191,8 @@
+
+
@@ -270,10 +272,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters
index 6cc20782..405c7f97 100644
--- a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters
+++ b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters
@@ -7,6 +7,12 @@
ballistica\base\app_adapter
+
+ ballistica\base\app_adapter
+
+
+ ballistica\base\app_adapter
+
ballistica\base\app_adapter
@@ -244,18 +250,81 @@
ballistica\base\graphics\component
+
+ ballistica\base\graphics\gl
+
ballistica\base\graphics\gl
ballistica\base\graphics\gl
+
+ ballistica\base\graphics\gl
+
+
+ ballistica\base\graphics\gl
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl
+
ballistica\base\graphics\gl
ballistica\base\graphics\gl
+
+ ballistica\base\graphics\gl
+
ballistica\base\graphics
@@ -1880,6 +1949,8 @@
+
+
diff --git a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.user b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.user
new file mode 100755
index 00000000..e5ce7994
--- /dev/null
+++ b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.user
@@ -0,0 +1,19 @@
+
+
+
+ $(TargetDir)
+ WindowsLocalDebugger
+
+
+ $(TargetDir)
+ WindowsLocalDebugger
+
+
+ $(TargetDir)
+ WindowsLocalDebugger
+
+
+ $(TargetDir)
+ WindowsLocalDebugger
+
+
\ No newline at end of file
diff --git a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj
index 77d01538..0a1d3a3f 100644
--- a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj
+++ b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj
@@ -186,6 +186,8 @@
+
+
@@ -265,10 +267,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters
index 6cc20782..405c7f97 100644
--- a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters
+++ b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters
@@ -7,6 +7,12 @@
ballistica\base\app_adapter
+
+ ballistica\base\app_adapter
+
+
+ ballistica\base\app_adapter
+
ballistica\base\app_adapter
@@ -244,18 +250,81 @@
ballistica\base\graphics\component
+
+ ballistica\base\graphics\gl
+
ballistica\base\graphics\gl
ballistica\base\graphics\gl
+
+ ballistica\base\graphics\gl
+
+
+ ballistica\base\graphics\gl
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\mesh
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl\program
+
+
+ ballistica\base\graphics\gl
+
ballistica\base\graphics\gl
ballistica\base\graphics\gl
+
+ ballistica\base\graphics\gl
+
ballistica\base\graphics
@@ -1880,6 +1949,8 @@
+
+
diff --git a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.user b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.user
new file mode 100755
index 00000000..e5ce7994
--- /dev/null
+++ b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.user
@@ -0,0 +1,19 @@
+
+
+
+ $(TargetDir)
+ WindowsLocalDebugger
+
+
+ $(TargetDir)
+ WindowsLocalDebugger
+
+
+ $(TargetDir)
+ WindowsLocalDebugger
+
+
+ $(TargetDir)
+ WindowsLocalDebugger
+
+
\ No newline at end of file
diff --git a/config/projectconfig.json b/config/projectconfig.json
index 52789db5..a60bf91d 100644
--- a/config/projectconfig.json
+++ b/config/projectconfig.json
@@ -9,12 +9,12 @@
"src/ballistica/base/graphics/texture/dds.h",
"src/ballistica/base/graphics/texture/ktx.cc",
"src/ballistica/core/platform/android/android_gl3.h",
- "src/ballistica/core/platform/apple/app_delegate.h",
- "src/ballistica/core/platform/apple/scripting_bridge_music.h",
+ "src/ballistica/base/platform/apple/app_delegate.h",
+ "src/ballistica/base/platform/apple/scripting_bridge_music.h",
"src/ballistica/core/platform/android/utf8/checked.h",
"src/ballistica/core/platform/android/utf8/unchecked.h",
"src/ballistica/core/platform/android/utf8/core.h",
- "src/ballistica/core/platform/apple/sdl_main_mac.h",
+ "src/ballistica/base/platform/apple/sdl_main_mac.h",
"src/ballistica/base/platform/oculus/main_rift.cc",
"src/ballistica/core/platform/android/android_gl3.c"
],
diff --git a/config/spinoffconfig.py b/config/spinoffconfig.py
index be3fae88..41dda47b 100644
--- a/config/spinoffconfig.py
+++ b/config/spinoffconfig.py
@@ -246,6 +246,7 @@ ctx.filter_file_extensions = {
'.sh',
'.sln',
'.vcxproj',
+ '.user',
'.cmd',
'.hlsl',
'.gradle',
diff --git a/src/assets/ba_data/python/babase/__init__.py b/src/assets/ba_data/python/babase/__init__.py
index 9f79a21f..2f5ec2ac 100644
--- a/src/assets/ba_data/python/babase/__init__.py
+++ b/src/assets/ba_data/python/babase/__init__.py
@@ -27,6 +27,7 @@ from _babase import (
apptime,
apptimer,
AppTimer,
+ can_toggle_fullscreen,
charstr,
clipboard_get_text,
clipboard_has_text,
@@ -51,7 +52,6 @@ from _babase import (
get_string_width,
get_v1_cloud_log_file_path,
getsimplesound,
- has_gamma_control,
has_user_run_commands,
have_chars,
have_permission,
@@ -90,6 +90,8 @@ from _babase import (
shutdown_suppress_end,
shutdown_suppress_count,
SimpleSound,
+ supports_max_fps,
+ supports_vsync,
unlock_all_input,
user_agent_string,
Vec3,
@@ -192,6 +194,7 @@ __all__ = [
'apptimer',
'AppTimer',
'Call',
+ 'can_toggle_fullscreen',
'charstr',
'clipboard_get_text',
'clipboard_has_text',
@@ -230,7 +233,6 @@ __all__ = [
'getclass',
'getsimplesound',
'handle_leftover_v1_cloud_log_file',
- 'has_gamma_control',
'has_user_run_commands',
'have_chars',
'have_permission',
@@ -297,6 +299,8 @@ __all__ = [
'storagename',
'StringEditAdapter',
'StringEditSubsystem',
+ 'supports_max_fps',
+ 'supports_vsync',
'TeamNotFoundError',
'timestring',
'UIScale',
diff --git a/src/assets/ba_data/python/babase/_app.py b/src/assets/ba_data/python/babase/_app.py
index ac366c04..befcb788 100644
--- a/src/assets/ba_data/python/babase/_app.py
+++ b/src/assets/ba_data/python/babase/_app.py
@@ -736,7 +736,7 @@ class App:
if self.state is self.State.PAUSED:
self._on_resume()
- # Handle initially entering or returning to other states.
+ # Entering or returning to running state
if self._initial_sign_in_completed and self._meta_scan_completed:
if self.state != self.State.RUNNING:
self.state = self.State.RUNNING
@@ -744,6 +744,7 @@ class App:
if not self._called_on_running:
self._called_on_running = True
self._on_running()
+ # Entering or returning to loading state:
elif self._init_completed:
if self.state is not self.State.LOADING:
self.state = self.State.LOADING
@@ -751,6 +752,8 @@ class App:
if not self._called_on_loading:
self._called_on_loading = True
self._on_loading()
+
+ # Entering or returning to initing state:
elif self._native_bootstrapping_completed:
if self.state is not self.State.INITING:
self.state = self.State.INITING
@@ -758,13 +761,21 @@ class App:
if not self._called_on_initing:
self._called_on_initing = True
self._on_initing()
- else:
- # Only possibility left is app-start. We shouldn't be
- # getting called before at least that happens.
- assert self._native_start_called
- assert self.state is self.State.NOT_RUNNING
- if bool(True):
+
+ # Entering or returning to native bootstrapping:
+ elif self._native_start_called:
+ if self.state is not self.State.NATIVE_BOOTSTRAPPING:
self.state = self.State.NATIVE_BOOTSTRAPPING
+ _babase.lifecyclelog('app state native bootstrapping')
+ else:
+ # Only logical possibility left is NOT_RUNNING, in which
+ # case we should not be getting called.
+ logging.warning(
+ 'App._update_state called while in %s state;'
+ ' should not happen.',
+ self.state.value,
+ stack_info=True,
+ )
async def _shutdown(self) -> None:
import asyncio
diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py
index 3a5cd63b..ad6f61ac 100644
--- a/src/assets/ba_data/python/baenv.py
+++ b/src/assets/ba_data/python/baenv.py
@@ -52,7 +52,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be
# using.
-TARGET_BALLISTICA_BUILD = 21342
+TARGET_BALLISTICA_BUILD = 21385
TARGET_BALLISTICA_VERSION = '1.7.28'
diff --git a/src/assets/ba_data/python/bauiv1/__init__.py b/src/assets/ba_data/python/bauiv1/__init__.py
index 6dde577d..9c8cc58d 100644
--- a/src/assets/ba_data/python/bauiv1/__init__.py
+++ b/src/assets/ba_data/python/bauiv1/__init__.py
@@ -31,6 +31,7 @@ from babase import (
apptimer,
AppTimer,
Call,
+ can_toggle_fullscreen,
charstr,
clipboard_is_supported,
clipboard_set_text,
@@ -52,7 +53,6 @@ from babase import (
get_string_width,
get_type_name,
getclass,
- has_gamma_control,
have_permission,
in_logic_thread,
increment_analytics_count,
@@ -76,6 +76,8 @@ from babase import (
set_low_level_config_value,
set_ui_input_device,
SpecialChar,
+ supports_max_fps,
+ supports_vsync,
timestring,
UIScale,
unlock_all_input,
@@ -136,6 +138,7 @@ __all__ = [
'buttonwidget',
'Call',
'can_show_ad',
+ 'can_toggle_fullscreen',
'charstr',
'checkboxwidget',
'clipboard_is_supported',
@@ -165,7 +168,6 @@ __all__ = [
'getmesh',
'getsound',
'gettexture',
- 'has_gamma_control',
'has_video_ads',
'have_incentivized_ad',
'have_permission',
@@ -205,6 +207,8 @@ __all__ = [
'show_online_score_ui',
'Sound',
'SpecialChar',
+ 'supports_max_fps',
+ 'supports_vsync',
'Texture',
'textwidget',
'timestring',
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/advanced.py b/src/assets/ba_data/python/bauiv1lib/settings/advanced.py
index aa00b581..930ecaa3 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/advanced.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/advanced.py
@@ -243,6 +243,7 @@ class AdvancedSettingsWindow(bui.Window):
# Don't rebuild if the menu is open or if our language and
# language-list hasn't changed.
+
# NOTE - although we now support widgets updating their own
# translations, we still change the label formatting on the language
# menu based on the language so still need this. ...however we could
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/graphics.py b/src/assets/ba_data/python/bauiv1lib/settings/graphics.py
index f95a5547..ec6879b4 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/graphics.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/graphics.py
@@ -4,12 +4,15 @@
from __future__ import annotations
-import logging
+from typing import TYPE_CHECKING, cast
from bauiv1lib.popup import PopupMenu
-from bauiv1lib.config import ConfigCheckBox, ConfigNumberEdit
+from bauiv1lib.config import ConfigCheckBox
import bauiv1 as bui
+if TYPE_CHECKING:
+ from typing import Any
+
class GraphicsSettingsWindow(bui.Window):
"""Window for graphics settings."""
@@ -42,23 +45,23 @@ class GraphicsSettingsWindow(bui.Window):
uiscale = app.ui_v1.uiscale
width = 450.0
height = 302.0
+ self._max_fps_dirty = False
+ self._last_max_fps_set_time = bui.apptime()
+ self._last_max_fps_str = ''
self._show_fullscreen = False
fullscreen_spacing_top = spacing * 0.2
fullscreen_spacing = spacing * 1.2
- if uiscale == bui.UIScale.LARGE and app.classic.platform != 'android':
+ if bui.can_toggle_fullscreen():
self._show_fullscreen = True
height += fullscreen_spacing + fullscreen_spacing_top
- show_gamma = False
- gamma_spacing = spacing * 1.3
- if bui.has_gamma_control():
- show_gamma = True
- height += gamma_spacing
+ show_vsync = bui.supports_vsync()
+ show_tv_mode = not bui.app.env.vr
- show_vsync = False
- if app.classic.platform == 'mac':
- show_vsync = True
+ show_max_fps = bui.supports_max_fps()
+ if show_max_fps:
+ height += 50
show_resolution = True
if app.env.vr:
@@ -70,7 +73,7 @@ class GraphicsSettingsWindow(bui.Window):
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
base_scale = (
- 2.4
+ 2.0
if uiscale is bui.UIScale.SMALL
else 1.5
if uiscale is bui.UIScale.MEDIUM
@@ -91,19 +94,20 @@ class GraphicsSettingsWindow(bui.Window):
)
)
- btn = bui.buttonwidget(
+ back_button = bui.buttonwidget(
parent=self._root_widget,
position=(35, height - 50),
- size=(120, 60),
+ # size=(120, 60),
+ size=(60, 60),
scale=0.8,
text_scale=1.2,
autoselect=True,
- label=bui.Lstr(resource='backText'),
- button_type='back',
+ label=bui.charstr(bui.SpecialChar.BACK),
+ button_type='backSmall',
on_activate_call=self._back,
)
- bui.containerwidget(edit=self._root_widget, cancel_button=btn)
+ bui.containerwidget(edit=self._root_widget, cancel_button=back_button)
bui.textwidget(
parent=self._root_widget,
@@ -115,15 +119,7 @@ class GraphicsSettingsWindow(bui.Window):
v_align='top',
)
- bui.buttonwidget(
- edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=bui.charstr(bui.SpecialChar.BACK),
- )
-
self._fullscreen_checkbox: bui.Widget | None = None
- self._gamma_controls: ConfigNumberEdit | None = None
if self._show_fullscreen:
v -= fullscreen_spacing_top
self._fullscreen_checkbox = ConfigCheckBox(
@@ -149,34 +145,10 @@ class GraphicsSettingsWindow(bui.Window):
self._have_selected_child = True
v -= fullscreen_spacing
- if show_gamma:
- self._gamma_controls = gmc = ConfigNumberEdit(
- parent=self._root_widget,
- position=(90, v),
- configkey='Screen Gamma',
- displayname=bui.Lstr(resource=self._r + '.gammaText'),
- minval=0.1,
- maxval=2.0,
- increment=0.1,
- xoffset=-70,
- textscale=0.85,
- )
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=gmc.plusbutton,
- right_widget=bui.get_special_widget('party_button'),
- )
- if not self._have_selected_child:
- bui.containerwidget(
- edit=self._root_widget, selected_child=gmc.minusbutton
- )
- self._have_selected_child = True
- v -= gamma_spacing
-
self._selected_color = (0.5, 1, 0.5, 1)
self._unselected_color = (0.7, 0.7, 0.7, 1)
- # quality
+ # Quality
bui.textwidget(
parent=self._root_widget,
position=(60, v),
@@ -208,7 +180,7 @@ class GraphicsSettingsWindow(bui.Window):
on_value_change_call=self._set_quality,
)
- # texture controls
+ # Texture controls
bui.textwidget(
parent=self._root_widget,
position=(230, v),
@@ -244,8 +216,9 @@ class GraphicsSettingsWindow(bui.Window):
h_offs = 0
+ resolution_popup: PopupMenu | None = None
+
if show_resolution:
- # resolution
bui.textwidget(
parent=self._root_widget,
position=(h_offs + 60, v),
@@ -258,32 +231,17 @@ class GraphicsSettingsWindow(bui.Window):
v_align='center',
)
- # on standard android we have 'Auto', 'Native', and a few
- # HD standards
+ # On standard android we have 'Auto', 'Native', and a few
+ # HD standards.
if app.classic.platform == 'android':
# on cardboard/daydream android we have a few
# render-target-scale options
if app.classic.subplatform == 'cardboard':
+ rawval = bui.app.config.resolve('GVR Render Target Scale')
current_res_cardboard = (
- str(
- min(
- 100,
- max(
- 10,
- int(
- round(
- bui.app.config.resolve(
- 'GVR Render Target Scale'
- )
- * 100.0
- )
- ),
- ),
- )
- )
- + '%'
+ str(min(100, max(10, int(round(rawval * 100.0))))) + '%'
)
- PopupMenu(
+ resolution_popup = PopupMenu(
parent=self._root_widget,
position=(h_offs + 60, v - 50),
width=120,
@@ -301,16 +259,16 @@ class GraphicsSettingsWindow(bui.Window):
bui.Lstr(resource='nativeText'),
]
for res in [1440, 1080, 960, 720, 480]:
- # nav bar is 72px so lets allow for that in what
- # choices we show
+ # Nav bar is 72px so lets allow for that in what
+ # choices we show.
if native_res[1] >= res - 72:
- res_str = str(res) + 'p'
+ res_str = f'{res}p'
choices.append(res_str)
choices_display.append(bui.Lstr(value=res_str))
current_res_android = bui.app.config.resolve(
'Resolution (Android)'
)
- PopupMenu(
+ resolution_popup = PopupMenu(
parent=self._root_widget,
position=(h_offs + 60, v - 50),
width=120,
@@ -325,26 +283,11 @@ class GraphicsSettingsWindow(bui.Window):
# set pixel-scale instead.
current_res = bui.get_display_resolution()
if current_res is None:
+ rawval = bui.app.config.resolve('Screen Pixel Scale')
current_res2 = (
- str(
- min(
- 100,
- max(
- 10,
- int(
- round(
- bui.app.config.resolve(
- 'Screen Pixel Scale'
- )
- * 100.0
- )
- ),
- ),
- )
- )
- + '%'
+ str(min(100, max(10, int(round(rawval * 100.0))))) + '%'
)
- PopupMenu(
+ resolution_popup = PopupMenu(
parent=self._root_widget,
position=(h_offs + 60, v - 50),
width=120,
@@ -355,11 +298,16 @@ class GraphicsSettingsWindow(bui.Window):
)
else:
raise RuntimeError(
- 'obsolete path; discrete resolutions'
+ 'obsolete code path; discrete resolutions'
' no longer supported'
)
+ if resolution_popup is not None:
+ bui.widget(
+ edit=resolution_popup.get_button(),
+ left_widget=back_button,
+ )
- # vsync
+ vsync_popup: PopupMenu | None = None
if show_vsync:
bui.textwidget(
parent=self._root_widget,
@@ -372,8 +320,7 @@ class GraphicsSettingsWindow(bui.Window):
h_align='center',
v_align='center',
)
-
- PopupMenu(
+ vsync_popup = PopupMenu(
parent=self._root_widget,
position=(230, v - 50),
width=150,
@@ -387,8 +334,59 @@ class GraphicsSettingsWindow(bui.Window):
current_choice=bui.app.config.resolve('Vertical Sync'),
on_value_change_call=self._set_vsync,
)
+ if resolution_popup is not None:
+ bui.widget(
+ edit=vsync_popup.get_button(),
+ left_widget=resolution_popup.get_button(),
+ )
+
+ if resolution_popup is not None and vsync_popup is not None:
+ bui.widget(
+ edit=resolution_popup.get_button(),
+ right_widget=vsync_popup.get_button(),
+ )
v -= 90
+ self._max_fps_text: bui.Widget | None = None
+ if show_max_fps:
+ v -= 5
+ bui.textwidget(
+ parent=self._root_widget,
+ position=(155, v + 10),
+ size=(0, 0),
+ text=bui.Lstr(resource=self._r + '.maxFPSText'),
+ color=bui.app.ui_v1.heading_color,
+ scale=0.9,
+ maxwidth=90,
+ h_align='right',
+ v_align='center',
+ )
+
+ max_fps_str = str(bui.app.config.resolve('Max FPS'))
+ self._last_max_fps_str = max_fps_str
+ self._max_fps_text = bui.textwidget(
+ parent=self._root_widget,
+ position=(170, v - 5),
+ size=(105, 30),
+ text=max_fps_str,
+ max_chars=5,
+ editable=True,
+ h_align='left',
+ v_align='center',
+ on_return_press_call=self._on_max_fps_return_press,
+ )
+ v -= 45
+
+ if self._max_fps_text is not None and resolution_popup is not None:
+ bui.widget(
+ edit=resolution_popup.get_button(),
+ down_widget=self._max_fps_text,
+ )
+ bui.widget(
+ edit=self._max_fps_text,
+ up_widget=resolution_popup.get_button(),
+ )
+
fpsc = ConfigCheckBox(
parent=self._root_widget,
position=(69, v - 6),
@@ -398,9 +396,17 @@ class GraphicsSettingsWindow(bui.Window):
displayname=bui.Lstr(resource=self._r + '.showFPSText'),
maxwidth=130,
)
+ if self._max_fps_text is not None:
+ bui.widget(
+ edit=self._max_fps_text,
+ down_widget=fpsc.widget,
+ )
+ bui.widget(
+ edit=fpsc.widget,
+ up_widget=self._max_fps_text,
+ )
- # (tv mode doesnt apply to vr)
- if not bui.app.env.vr:
+ if show_tv_mode:
tvc = ConfigCheckBox(
parent=self._root_widget,
position=(240, v - 6),
@@ -410,13 +416,8 @@ class GraphicsSettingsWindow(bui.Window):
displayname=bui.Lstr(resource=self._r + '.tvBorderText'),
maxwidth=130,
)
- # grumble..
bui.widget(edit=fpsc.widget, right_widget=tvc.widget)
- try:
- pass
-
- except Exception:
- logging.exception('Exception wiring up graphics settings UI.')
+ bui.widget(edit=tvc.widget, left_widget=fpsc.widget)
v -= spacing
@@ -429,6 +430,10 @@ class GraphicsSettingsWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.settings import allsettings
+ # Applying max-fps takes a few moments. Apply if it hasn't been
+ # yet.
+ self._apply_max_fps()
+
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
@@ -469,7 +474,60 @@ class GraphicsSettingsWindow(bui.Window):
cfg['Vertical Sync'] = val
cfg.apply_and_commit()
+ def _on_max_fps_return_press(self) -> None:
+ self._apply_max_fps()
+ bui.containerwidget(
+ edit=self._root_widget, selected_child=cast(bui.Widget, 0)
+ )
+
+ def _apply_max_fps(self) -> None:
+ if not self._max_fps_dirty or not self._max_fps_text:
+ return
+
+ val: Any = bui.textwidget(query=self._max_fps_text)
+ assert isinstance(val, str)
+ # If there's a broken value, replace it with the default.
+ try:
+ ival = int(val)
+ except ValueError:
+ ival = bui.app.config.default_value('Max FPS')
+ assert isinstance(ival, int)
+
+ # Clamp to reasonable limits (allow -1 to mean no max).
+ if ival != -1:
+ ival = max(10, ival)
+ ival = min(99999, ival)
+
+ # Store it to the config.
+ cfg = bui.app.config
+ cfg['Max FPS'] = ival
+ cfg.apply_and_commit()
+
+ # Update the display if we changed the value.
+ if str(ival) != val:
+ bui.textwidget(edit=self._max_fps_text, text=str(ival))
+
+ self._max_fps_dirty = False
+
def _update_controls(self) -> None:
+ if self._max_fps_text is not None:
+ # Keep track of when the max-fps value changes. Once it
+ # remains stable for a few moments, apply it.
+ val: Any = bui.textwidget(query=self._max_fps_text)
+ assert isinstance(val, str)
+ if val != self._last_max_fps_str:
+ # Oop; it changed. Note the time and the fact that we'll
+ # need to apply it at some point.
+ self._max_fps_dirty = True
+ self._last_max_fps_str = val
+ self._last_max_fps_set_time = bui.apptime()
+ else:
+ # If its been stable long enough, apply it.
+ if (
+ self._max_fps_dirty
+ and bui.apptime() - self._last_max_fps_set_time > 1.0
+ ):
+ self._apply_max_fps()
if self._show_fullscreen:
bui.checkboxwidget(
edit=self._fullscreen_checkbox,
diff --git a/src/ballistica/base/app_adapter/app_adapter.cc b/src/ballistica/base/app_adapter/app_adapter.cc
index 824ea797..db79fbba 100644
--- a/src/ballistica/base/app_adapter/app_adapter.cc
+++ b/src/ballistica/base/app_adapter/app_adapter.cc
@@ -2,11 +2,19 @@
#include "ballistica/base/app_adapter/app_adapter.h"
+#if BA_OSTYPE_ANDROID
+#include "ballistica/base/app_adapter/app_adapter_android.h"
+#endif
+#include "ballistica/base/app_adapter/app_adapter_apple.h"
+#include "ballistica/base/app_adapter/app_adapter_headless.h"
+#include "ballistica/base/app_adapter/app_adapter_sdl.h"
+#include "ballistica/base/app_adapter/app_adapter_vr.h"
#include "ballistica/base/graphics/graphics_server.h"
#include "ballistica/base/graphics/renderer/renderer.h"
#include "ballistica/base/input/input.h"
#include "ballistica/base/networking/network_reader.h"
#include "ballistica/base/networking/networking.h"
+#include "ballistica/base/platform/base_platform.h"
#include "ballistica/base/support/stress_test.h"
#include "ballistica/base/ui/ui.h"
#include "ballistica/shared/foundation/event_loop.h"
@@ -14,20 +22,47 @@
namespace ballistica::base {
+auto AppAdapter::Create() -> AppAdapter* {
+ assert(g_core);
+
+// TEMP - need to init sdl on our legacy mac build even though its not
+// technically an SDL app. Kill this once the old mac build is gone.
+#if BA_LEGACY_MACOS_BUILD
+ AppAdapterSDL::InitSDL();
+#endif
+
+ AppAdapter* app_adapter{};
+
+#if BA_HEADLESS_BUILD
+ app_adapter = new AppAdapterHeadless();
+#elif BA_OSTYPE_ANDROID
+ app_adapter = new AppAdapterAndroid();
+#elif BA_XCODE_BUILD
+ app_adapter = new AppAdapterApple();
+#elif BA_RIFT_BUILD
+ // Rift build can spin up in either VR or regular mode.
+ if (g_core->vr_mode) {
+ app_adapter = new AppAdapterVR();
+ } else {
+ app_adapter = new AppAdapterSDL();
+ }
+#elif BA_CARDBOARD_BUILD
+ app_adapter = new AppAdapterVR();
+#elif BA_SDL_BUILD
+ app_adapter = new AppAdapterSDL();
+#else
+#error No app adapter defined for this build.
+#endif
+
+ assert(app_adapter);
+ return app_adapter;
+}
+
AppAdapter::AppAdapter() = default;
AppAdapter::~AppAdapter() = default;
-void AppAdapter::LogicThreadDoApplyAppConfig() {
- assert(g_base->InLogicThread());
-}
-
-auto AppAdapter::ManagesEventLoop() const -> bool {
- // We have 2 redundant values for essentially the same thing;
- // should get rid of IsEventPushMode() once we've created
- // App subclasses for our various platforms.
- return !g_core->platform->IsEventPushMode();
-}
+auto AppAdapter::ManagesMainThreadEventLoop() const -> bool { return true; }
void AppAdapter::OnMainThreadStartApp() {
assert(g_base);
@@ -37,11 +72,12 @@ void AppAdapter::OnMainThreadStartApp() {
// Add some common input devices where applicable. More specific ones (SDL
// Joysticks, etc.) get added in subclasses.
- // If we've got a nice themed hardware cursor, show it. Otherwise we'll
- // render it manually, which is laggier but gets the job done.
- g_core->platform->SetHardwareCursorVisible(g_buildconfig.hardware_cursor());
-
+ // FIXME: This stuff should probably go elsewhere.
if (!g_core->HeadlessMode()) {
+ // If we've got a nice themed hardware cursor, show it. Otherwise we'll
+ // render it manually, which is laggier but gets the job done.
+ g_base->platform->SetHardwareCursorVisible(g_buildconfig.hardware_cursor());
+
// On desktop systems we just assume keyboard input exists and add it
// immediately.
if (g_core->platform->IsRunningOnDesktop()) {
@@ -56,74 +92,82 @@ void AppAdapter::OnMainThreadStartApp() {
}
}
-void AppAdapter::DrawFrame(bool during_resize) {
- assert(g_base->InGraphicsThread());
+void AppAdapter::OnAppStart() { assert(g_base->InLogicThread()); }
+void AppAdapter::OnAppPause() { assert(g_base->InLogicThread()); }
+void AppAdapter::OnAppResume() { assert(g_base->InLogicThread()); }
+void AppAdapter::OnAppShutdown() { assert(g_base->InLogicThread()); }
+void AppAdapter::OnAppShutdownComplete() { assert(g_base->InLogicThread()); }
+void AppAdapter::OnScreenSizeChange() { assert(g_base->InLogicThread()); }
+void AppAdapter::DoApplyAppConfig() { assert(g_base->InLogicThread()); }
- // It's possible to be asked to draw before we're ready.
- if (!g_base->graphics_server || !g_base->graphics_server->renderer()) {
- return;
- }
+// void AppAdapter::DrawFrame(bool during_resize) {
+// assert(g_base->InGraphicsThread());
- millisecs_t starttime = g_core->GetAppTimeMillisecs();
+// // It's possible to be asked to draw before we're ready.
+// if (!g_base->graphics_server || !g_base->graphics_server->renderer()) {
+// return;
+// }
- // A resize-draw event means that we're drawing due to a window resize.
- // In this case we ignore regular draw events for a short while
- // afterwards which makes resizing smoother.
- //
- // FIXME: should figure out the *correct* way to handle this; I believe
- // the underlying cause here is some sort of context contention across
- // threads.
- if (during_resize) {
- last_resize_draw_event_time_ = starttime;
- } else {
- if (starttime - last_resize_draw_event_time_ < (1000 / 30)) {
- return;
- }
- }
- g_base->graphics_server->TryRender();
- RunRenderUpkeepCycle();
-}
+// millisecs_t starttime = g_core->GetAppTimeMillisecs();
-void AppAdapter::RunRenderUpkeepCycle() {
- // This should only be firing if the OS is handling the event loop.
- assert(!ManagesEventLoop());
+// // A resize-draw event means that we're drawing due to a window resize.
+// // In this case we ignore regular draw events for a short while
+// // afterwards which makes resizing smoother.
+// //
+// // FIXME: should figure out the *correct* way to handle this; I believe
+// // the underlying cause here is some sort of context contention across
+// // threads.
+// if (during_resize) {
+// last_resize_draw_event_time_ = starttime;
+// } else {
+// if (starttime - last_resize_draw_event_time_ < (1000 / 30)) {
+// return;
+// }
+// }
+// g_base->graphics_server->TryRender();
+// // RunRenderUpkeepCycle();
+// }
- // Pump the main event loop (when we're being driven by frame-draw
- // callbacks, this is the only place that gets done).
- g_core->main_event_loop()->RunSingleCycle();
+// void AppAdapter::RunRenderUpkeepCycle() {
+// // This should only be firing if the OS is handling the event loop.
+// assert(!ManagesMainThreadEventLoop());
- // Now do the general app event cycle for whoever needs to process things.
- // FIXME KILL THIS.
- RunEvents();
-}
+// // Pump the main event loop (when we're being driven by frame-draw
+// // callbacks, this is the only place that gets done).
+// g_core->main_event_loop()->RunSingleCycle();
+
+// // Now do the general app event cycle for whoever needs to process things.
+// // FIXME KILL THIS.
+// RunEvents();
+// }
// FIXME KILL THIS.
-void AppAdapter::RunEvents() {
- // There's probably a better place for this.
- g_base->stress_test()->Update();
+// void AppAdapter::RunEvents() {
+// There's probably a better place for this.
+// g_base->stress_test()->Update();
- // Give platforms a chance to pump/handle their own events.
- //
- // FIXME: now that we have app class overrides, platform should really not
- // be doing event handling. (need to fix Rift build in this regard).
- g_core->platform->RunEvents();
-}
+// Give platforms a chance to pump/handle their own events.
+//
+// FIXME: now that we have app class overrides, platform should really not
+// be doing event handling. (need to fix Rift build in this regard).
+// g_core->platform->RunEvents();
+// }
-void AppAdapter::UpdatePauseResume_() {
- if (app_paused_) {
- // Unpause if no one wants pause.
- if (!app_pause_requested_) {
- OnAppResume_();
- app_paused_ = false;
- }
- } else {
- // OnAppPause if anyone wants.
- if (app_pause_requested_) {
- OnAppPause_();
- app_paused_ = true;
- }
- }
-}
+// void AppAdapter::UpdatePauseResume_() {
+// if (app_paused_) {
+// // Unpause if no one wants pause.
+// if (!app_pause_requested_) {
+// OnAppResume_();
+// app_paused_ = false;
+// }
+// } else {
+// // OnAppPause if anyone wants.
+// if (app_pause_requested_) {
+// OnAppPause_();
+// app_paused_ = true;
+// }
+// }
+// }
void AppAdapter::OnAppPause_() {
assert(g_core->InMainThread());
@@ -144,7 +188,7 @@ void AppAdapter::OnAppPause_() {
void AppAdapter::OnAppResume_() {
assert(g_core->InMainThread());
- last_app_resume_time_ = g_core->GetAppTimeMillisecs();
+ // last_app_resume_time_ = g_core->GetAppTimeMillisecs();
// Spin all event-loops back up.
EventLoop::SetEventLoopsPaused(false);
@@ -173,6 +217,13 @@ void AppAdapter::OnAppResume_() {
void AppAdapter::PauseApp() {
assert(g_core);
assert(g_core->InMainThread());
+
+ if (app_paused_) {
+ Log(LogLevel::kWarning,
+ "AppAdapter::PauseApp() called with app already paused.");
+ return;
+ }
+
millisecs_t start_time{core::CorePlatform::GetCurrentMillisecs()};
// Apple mentioned 5 seconds to run stuff once backgrounded or they bring
@@ -181,9 +232,10 @@ void AppAdapter::PauseApp() {
g_core->platform->DebugLog(
"PauseApp@" + std::to_string(core::CorePlatform::GetCurrentMillisecs()));
- assert(!app_pause_requested_);
- app_pause_requested_ = true;
- UpdatePauseResume_();
+ // assert(!app_pause_requested_);
+ // app_pause_requested_ = true;
+ OnAppPause_();
+ // UpdatePauseResume_();
// We assume that the OS will completely suspend our process the moment we
// return from this call (though this is not technically true on all
@@ -219,13 +271,21 @@ void AppAdapter::PauseApp() {
}
void AppAdapter::ResumeApp() {
- assert(g_core && g_core->InMainThread());
+ assert(g_core);
+ assert(g_core->InMainThread());
+
+ if (!app_paused_) {
+ Log(LogLevel::kWarning,
+ "AppAdapter::ResumeApp() called with app not in paused state.");
+ return;
+ }
millisecs_t start_time{core::CorePlatform::GetCurrentMillisecs()};
g_core->platform->DebugLog(
"ResumeApp@" + std::to_string(core::CorePlatform::GetCurrentMillisecs()));
- assert(app_pause_requested_);
- app_pause_requested_ = false;
- UpdatePauseResume_();
+ // assert(app_pause_requested_);
+ // app_pause_requested_ = false;
+ // UpdatePauseResume_();
+ OnAppResume_();
if (g_buildconfig.debug_build()) {
Log(LogLevel::kDebug,
"ResumeApp() completed in "
@@ -235,21 +295,18 @@ void AppAdapter::ResumeApp() {
}
}
-void AppAdapter::DidFinishRenderingFrame(FrameDef* frame) {}
-
-void AppAdapter::PrimeMainThreadEventPump() {
- assert(!ManagesEventLoop());
-
- // Need to release the GIL while we're doing this so other thread
- // can do their Python-y stuff.
- Python::ScopedInterpreterLockRelease release;
-
- // Pump events manually until a screen gets created.
- // At that point we use frame-draws to drive our event loop.
- while (!g_base->graphics_server->initial_screen_created()) {
- g_core->main_event_loop()->RunSingleCycle();
- core::CorePlatform::SleepMillisecs(1);
- }
+void AppAdapter::RunMainThreadEventLoopToCompletion() {
+ FatalError("RunMainThreadEventLoopToCompletion is not implemented here.");
}
+void AppAdapter::DoExitMainThreadEventLoop() {
+ FatalError("DoExitMainThreadEventLoop is not implemented here.");
+}
+
+auto AppAdapter::CanToggleFullscreen() -> bool const { return false; }
+
+auto AppAdapter::SupportsVSync() -> bool const { return false; }
+
+auto AppAdapter::SupportsMaxFPS() -> bool const { return false; }
+
} // namespace ballistica::base
diff --git a/src/ballistica/base/app_adapter/app_adapter.h b/src/ballistica/base/app_adapter/app_adapter.h
index a7b0e7af..2ef9880e 100644
--- a/src/ballistica/base/app_adapter/app_adapter.h
+++ b/src/ballistica/base/app_adapter/app_adapter.h
@@ -3,40 +3,54 @@
#ifndef BALLISTICA_BASE_APP_ADAPTER_APP_ADAPTER_H_
#define BALLISTICA_BASE_APP_ADAPTER_APP_ADAPTER_H_
-#include
-
#include "ballistica/base/base.h"
+#include "ballistica/shared/generic/lambda_runnable.h"
namespace ballistica::base {
/// Adapts app behavior specific to a particular paradigm and/or api
-/// environment. For example, 'Headless', 'VROculus', 'SDL', etc. Multiple
-/// of these may be supported on a single platform, unlike the Platform
-/// classes where generally there is a single one for the whole platform.
-/// For example, on Windows, we might have GUI, VR, and Headless
-/// AppAdapters, but they all might share the same CorePlatform and
-/// BasePlatform classes.
+/// environment. For example, 'Headless', 'VROculus', 'SDL', etc. These may
+/// be mixed & matched with platform classes to define a build. For example,
+/// on Windows, we might have SDL, VR, and Headless AppAdapters, but they
+/// all might share the same CorePlatform and BasePlatform classes.
class AppAdapter {
public:
- AppAdapter();
- virtual ~AppAdapter();
+ /// Instantiate the AppAdapter subclass for the current build.
+ static auto Create() -> AppAdapter*;
+ /// Called in the main thread when the app is being started.
virtual void OnMainThreadStartApp();
- /// Return whether this class runs its own event loop.
- auto ManagesEventLoop() const -> bool;
+ // Logic thread callbacks.
+ virtual void OnAppStart();
+ virtual void OnAppPause();
+ virtual void OnAppResume();
+ virtual void OnAppShutdown();
+ virtual void OnAppShutdownComplete();
+ virtual void OnScreenSizeChange();
+ virtual void DoApplyAppConfig();
- /// Called for non-event-loop-managing apps to give them an opportunity to
- /// ensure they are self-sustaining. For instance, an app relying on
- /// frame-draws for its main thread event processing may need to manually
- /// pump events until a screen-creation event goes through which should
- /// keep things running thereafter.
- virtual void PrimeMainThreadEventPump();
+ /// Return whether this class manages the main thread event loop itself.
+ /// Default is true. If this is true, RunMainThreadEventLoopToCompletion()
+ /// will be called to run the app. This should return false on builds
+ /// where the OS manages the main thread event loop and we just sit in it
+ /// and receive events via callbacks/etc.
+ virtual auto ManagesMainThreadEventLoop() const -> bool;
- /// Handle any pending OS events. On normal graphical builds this is
- /// triggered by RunRenderUpkeepCycle(); timer intervals for headless
- /// builds, etc. Should process any pending OS events, etc.
- virtual void RunEvents();
+ /// When called, the main thread event loop should be run until
+ /// ExitMainThreadEventLoop() is called. This will only be called if
+ /// ManagesMainThreadEventLoop() returns true.
+ virtual void RunMainThreadEventLoopToCompletion();
+
+ /// Called when the main thread event loop should exit. Will only be
+ /// called if ManagesMainThreadEventLoop() returns true.
+ virtual void DoExitMainThreadEventLoop();
+
+ /// Push a call to be run in the app's main thread.
+ template
+ void PushMainThreadCall(const F& lambda) {
+ DoPushMainThreadRunnable(NewLambdaRunnableUnmanaged(lambda));
+ }
/// Put the app into a paused state. Should be called from the main
/// thread. Pauses work, closes network sockets, etc. May correspond to
@@ -53,34 +67,29 @@ class AppAdapter {
auto app_paused() const { return app_paused_; }
- /// The last time the app was resumed (uses GetAppTimeMillisecs() value).
- auto last_app_resume_time() const { return last_app_resume_time_; }
+ /// Return whether this AppAdapter supports a 'fullscreen' toggle for its
+ /// display. This currently will simply affect whether that option is
+ /// available in display settings or via a hotkey.
+ virtual auto CanToggleFullscreen() -> bool const;
- /// Attempt to draw a frame.
- void DrawFrame(bool during_resize = false);
+ /// Return whether this AppAdapter supports vsync controls for its display.
+ virtual auto SupportsVSync() -> bool const;
- /// Gets called when the app config is being applied. Note that this call
- /// happens in the logic thread, so we should do any reading that needs to
- /// happen in the logic thread and then forward the values to ourself back
- /// in our main thread.
- virtual void LogicThreadDoApplyAppConfig();
+ /// Return whether this AppAdapter supports max-fps controls for its display.
+ virtual auto SupportsMaxFPS() -> bool const;
- /// Used on platforms where our main thread event processing is driven by
- /// frame-draw commands given to us. This should be called after drawing a
- /// frame in order to bring game state up to date and process OS events.
- void RunRenderUpkeepCycle();
+ protected:
+ AppAdapter();
+ virtual ~AppAdapter();
- /// Called by the graphics-server when drawing completes for a frame.
- virtual void DidFinishRenderingFrame(FrameDef* frame);
+ /// Push a raw pointer Runnable to the platform's 'main' thread. The main
+ /// thread should call its RunAndLogErrors() method and then delete it.
+ virtual void DoPushMainThreadRunnable(Runnable* runnable) = 0;
private:
- void UpdatePauseResume_();
void OnAppPause_();
void OnAppResume_();
- bool app_pause_requested_{};
bool app_paused_{};
- millisecs_t last_resize_draw_event_time_{};
- millisecs_t last_app_resume_time_{};
};
} // namespace ballistica::base
diff --git a/src/ballistica/base/app_adapter/app_adapter_apple.cc b/src/ballistica/base/app_adapter/app_adapter_apple.cc
new file mode 100644
index 00000000..19475ce2
--- /dev/null
+++ b/src/ballistica/base/app_adapter/app_adapter_apple.cc
@@ -0,0 +1,25 @@
+// Released under the MIT License. See LICENSE for details.
+#if BA_XCODE_BUILD
+
+#include "ballistica/base/app_adapter/app_adapter_apple.h"
+
+#include
+
+#include "ballistica/shared/ballistica.h"
+
+namespace ballistica::base {
+
+auto AppAdapterApple::ManagesMainThreadEventLoop() const -> bool {
+ // Nope; we run under a standard Cocoa/UIKit environment and they call us; we
+ // don't call them.
+ return false;
+}
+
+void AppAdapterApple::DoPushMainThreadRunnable(Runnable* runnable) {
+ // Kick this along to swift.
+ BallisticaKit::PushRawRunnableToMain(runnable);
+}
+
+} // namespace ballistica::base
+
+#endif // BA_XCODE_BUILD
diff --git a/src/ballistica/base/app_adapter/app_adapter_apple.h b/src/ballistica/base/app_adapter/app_adapter_apple.h
new file mode 100644
index 00000000..cf1297df
--- /dev/null
+++ b/src/ballistica/base/app_adapter/app_adapter_apple.h
@@ -0,0 +1,26 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_APP_ADAPTER_APP_ADAPTER_APPLE_H_
+#define BALLISTICA_BASE_APP_ADAPTER_APP_ADAPTER_APPLE_H_
+
+#if BA_XCODE_BUILD
+
+#include "ballistica/base/app_adapter/app_adapter.h"
+
+namespace ballistica::base {
+
+class AppAdapterApple : public AppAdapter {
+ public:
+ auto ManagesMainThreadEventLoop() const -> bool override;
+
+ protected:
+ void DoPushMainThreadRunnable(Runnable* runnable) override;
+
+ private:
+};
+
+} // namespace ballistica::base
+
+#endif // BA_XCODE_BUILD
+
+#endif // BALLISTICA_BASE_APP_ADAPTER_APP_ADAPTER_APPLE_H_
diff --git a/src/ballistica/base/app_adapter/app_adapter_headless.cc b/src/ballistica/base/app_adapter/app_adapter_headless.cc
index 90dc63a5..2b8339bb 100644
--- a/src/ballistica/base/app_adapter/app_adapter_headless.cc
+++ b/src/ballistica/base/app_adapter/app_adapter_headless.cc
@@ -3,20 +3,41 @@
#include "ballistica/base/app_adapter/app_adapter_headless.h"
+#include "ballistica/base/graphics/graphics_server.h"
#include "ballistica/shared/ballistica.h"
namespace ballistica::base {
-// We could technically use the vanilla App class here since we're not
-// changing anything.
-AppAdapterHeadless::AppAdapterHeadless() {
- // Handle a few misc things like stress-test updates.
- // (SDL builds set up a similar timer so we need to also).
- // This can probably go away at some point.
- g_core->main_event_loop()->NewTimer(10, true, NewLambdaRunnable([this] {
- assert(g_base->app_adapter);
- g_base->app_adapter->RunEvents();
- }));
+AppAdapterHeadless::AppAdapterHeadless() {}
+
+void AppAdapterHeadless::OnMainThreadStartApp() {
+ assert(g_core->InMainThread());
+
+ // We're not embedded into any sort of event system, so we just
+ // spin up our very own event loop for the main thread.
+ main_event_loop_ =
+ new EventLoop(EventLoopID::kMain, ThreadSource::kWrapCurrent);
+}
+
+void AppAdapterHeadless::DoApplyAppConfig() {
+ // Normal graphical app-adapters kick off screen creation here
+ // which then leads to remaining app bootstrapping. We create
+ // a 'null' screen purely for the same effect.
+ PushMainThreadCall([] { g_base->graphics_server->SetNullGraphics(); });
+}
+
+void AppAdapterHeadless::RunMainThreadEventLoopToCompletion() {
+ assert(g_core->InMainThread());
+ main_event_loop_->RunToCompletion();
+}
+
+void AppAdapterHeadless::DoPushMainThreadRunnable(Runnable* runnable) {
+ main_event_loop_->PushRunnable(runnable);
+}
+
+void AppAdapterHeadless::DoExitMainThreadEventLoop() {
+ assert(g_core->InMainThread());
+ main_event_loop_->Exit();
}
} // namespace ballistica::base
diff --git a/src/ballistica/base/app_adapter/app_adapter_headless.h b/src/ballistica/base/app_adapter/app_adapter_headless.h
index a45cbe3b..6d687bb2 100644
--- a/src/ballistica/base/app_adapter/app_adapter_headless.h
+++ b/src/ballistica/base/app_adapter/app_adapter_headless.h
@@ -12,6 +12,18 @@ namespace ballistica::base {
class AppAdapterHeadless : public AppAdapter {
public:
AppAdapterHeadless();
+
+ void OnMainThreadStartApp() override;
+
+ void DoApplyAppConfig() override;
+
+ protected:
+ void DoPushMainThreadRunnable(Runnable* runnable) override;
+ void RunMainThreadEventLoopToCompletion() override;
+ void DoExitMainThreadEventLoop() override;
+
+ private:
+ EventLoop* main_event_loop_{};
};
} // namespace ballistica::base
diff --git a/src/ballistica/base/app_adapter/app_adapter_sdl.cc b/src/ballistica/base/app_adapter/app_adapter_sdl.cc
index c12de592..a4a0551e 100644
--- a/src/ballistica/base/app_adapter/app_adapter_sdl.cc
+++ b/src/ballistica/base/app_adapter/app_adapter_sdl.cc
@@ -1,27 +1,206 @@
// Released under the MIT License. See LICENSE for details.
+#include "ballistica/shared/buildconfig/buildconfig_common.h"
#if BA_SDL_BUILD
#include "ballistica/base/app_adapter/app_adapter_sdl.h"
-
+#include "ballistica/base/base.h"
#include "ballistica/base/dynamics/bg/bg_dynamics.h"
#include "ballistica/base/graphics/gl/gl_sys.h"
+#include "ballistica/base/graphics/gl/renderer_gl.h"
+#include "ballistica/base/graphics/graphics.h"
#include "ballistica/base/graphics/graphics_server.h"
#include "ballistica/base/input/device/joystick_input.h"
#include "ballistica/base/input/input.h"
#include "ballistica/base/logic/logic.h"
#include "ballistica/base/python/base_python.h"
-#include "ballistica/base/support/stress_test.h"
+#include "ballistica/base/support/app_config.h"
#include "ballistica/base/ui/ui.h"
#include "ballistica/core/platform/core_platform.h"
#include "ballistica/shared/foundation/event_loop.h"
-#include "ballistica/shared/python/python.h"
namespace ballistica::base {
-void AppAdapterSDL::HandleSDLEvent(const SDL_Event& event) {
+AppAdapterSDL::AppAdapterSDL() {
+ assert(!g_core->HeadlessMode());
assert(g_core->InMainThread());
+ // Enable display-time debug logs via env var.
+ auto val = g_core->platform->GetEnv("BA_DEBUG_LOG_SDL_FRAME_TIMING");
+ if (val && *val == "1") {
+ debug_log_sdl_frame_timing_ = true;
+ }
+}
+
+void AppAdapterSDL::OnMainThreadStartApp() {
+ // App is starting. Let's fire up the ol' SDL.
+ uint32_t sdl_flags{SDL_INIT_VIDEO | SDL_INIT_JOYSTICK};
+
+ // We may or may not want xinput on windows.
+ if (g_buildconfig.ostype_windows()) {
+ if (!g_core->platform->GetLowLevelConfigValue("enablexinput", 1)) {
+ SDL_SetHint(SDL_HINT_XINPUT_ENABLED, "0");
+ }
+ }
+
+ int result = SDL_Init(sdl_flags);
+ if (result < 0) {
+ FatalError(std::string("SDL_Init failed: ") + SDL_GetError());
+ }
+
+ // Register events we can send ourself.
+ sdl_runnable_event_id_ = SDL_RegisterEvents(1);
+ assert(sdl_runnable_event_id_ != (uint32_t)-1);
+
+ // Note: parent class can add some input devices so need to bring up sdl
+ // before we let it run. That code should maybe be relocated/refactored.
+ AppAdapter::OnMainThreadStartApp();
+
+ if (g_buildconfig.enable_sdl_joysticks()) {
+ // We want events from joysticks.
+ SDL_JoystickEventState(SDL_ENABLE);
+
+ // Add already-existing SDL joysticks. Any added later will come
+ // through as joystick-added events.
+ //
+ // TODO(ericf): Check to see if this is necessary or if we always get
+ // connected events even for these initial ones.
+ if (explicit_bool(true)) {
+ int joystick_count = SDL_NumJoysticks();
+ for (int i = 0; i < joystick_count; i++) {
+ AppAdapterSDL::OnSDLJoystickAdded_(i);
+ }
+ }
+ }
+}
+
+void AppAdapterSDL::DoApplyAppConfig() {
+ assert(g_base->InLogicThread());
+
+ g_base->graphics_server->PushSetScreenPixelScaleCall(
+ g_base->app_config->Resolve(AppConfig::FloatID::kScreenPixelScale));
+
+ auto graphics_quality_requested =
+ g_base->graphics->GraphicsQualityFromAppConfig();
+
+ auto texture_quality_requested =
+ g_base->graphics->TextureQualityFromAppConfig();
+
+ // Android res string.
+ // std::string android_res =
+ // g_base->app_config->Resolve(AppConfig::StringID::kResolutionAndroid);
+
+ bool fullscreen = g_base->app_config->Resolve(AppConfig::BoolID::kFullscreen);
+ auto vsync = g_base->graphics->VSyncFromAppConfig();
+ int max_fps = g_base->app_config->Resolve(AppConfig::IntID::kMaxFPS);
+
+ // Tell the main thread to set up the screen with these settings.
+ g_base->app_adapter->PushMainThreadCall([=] {
+ SetScreen_(fullscreen, max_fps, vsync, texture_quality_requested,
+ graphics_quality_requested);
+ });
+}
+
+void AppAdapterSDL::RunMainThreadEventLoopToCompletion() {
+ assert(g_core->InMainThread());
+
+ // float smoothed_fps{};
+ // float fps_smoothing{0.1f};
+ while (!done_) {
+ microsecs_t cycle_start_time = g_core->GetAppTimeMicrosecs();
+
+ // Run all pending events.
+ SDL_Event event;
+ while (SDL_PollEvent(&event) && (!done_)) {
+ HandleSDLEvent_(event);
+ }
+
+ // Draw a frame.
+ if (g_base->graphics_server->TryRender()) {
+ SDL_GL_SwapWindow(sdl_window_);
+ }
+
+ // Sleep until we should start our next cycle (based on
+ // max-frame-rate or other factors).
+
+ // Special case which means no max. Farewell poor laptop battery.
+ if (max_fps_ == -1) {
+ continue;
+ }
+ microsecs_t now = g_core->GetAppTimeMicrosecs();
+ auto used_max_fps = max_fps_;
+ millisecs_t millisecs_per_frame = 1000000 / used_max_fps;
+ // Special case: if we've got vsync enabled, let's tweak max-fps to be
+ // just a *tiny* bit higher than requested. This means if our max-fps
+ // matches the refresh rate we'll be trying to render just a bit faster
+ // than vsync which should push us up against the vsync wall and keep
+ // vsync doing most of the delay work. In that case the logging below
+ // should show mostly 'no sleep.'. Without this delay, our render
+ // kick-offs tend to drift around the middle of the vsync cycle and I
+ // worry there could be bad interference patterns in certain spots close
+ // to the edges. Note that we want this tweak to be small enough that it
+ // won't be noticable in situations where vsync and max-fps *don't*
+ // match. (for instance limiting to 60hz on a 120hz vsynced monitor).
+ if (vsync_enabled_) {
+ millisecs_per_frame = 99 * millisecs_per_frame / 100;
+ }
+ microsecs_t target_time =
+ cycle_start_time + millisecs_per_frame - oversleep;
+
+ // Set a minimum so we don't sleep if we're within a few millisecs of
+ // where we want to be. Sleep tends to run over by a bit so we'll
+ // probably render closer to our target time by just skipping the sleep.
+ // And the oversleep system will compensate just as it does if we sleep
+ // too long.
+ const microsecs_t min_sleep{2000};
+ if (now + min_sleep >= target_time) {
+ if (debug_log_sdl_frame_timing_) {
+ Log(LogLevel::kDebug, "no sleep."); // 'till brooklyn!
+ }
+ } else {
+ if (debug_log_sdl_frame_timing_) {
+ char buf[256];
+ snprintf(buf, sizeof(buf), "render %.1f sleep %.1f",
+ (now - cycle_start_time) / 1000.0f,
+ (target_time - now) / 1000.0f);
+ Log(LogLevel::kDebug, buf);
+ }
+ g_core->platform->SleepMicrosecs(target_time - now);
+ }
+
+ // Maintain an 'oversleep' amount to compensate for the timer not being
+ // exact. This should keep us exactly at our target frame-rate in the
+ // end.
+ now = g_core->GetAppTimeMicrosecs();
+ oversleep = now - target_time;
+
+ // Prevent oversleep from compensating by more than a few millisecs per
+ // frame (not sure if this would ever be a problem but lets be safe).
+ oversleep = std::max(int64_t{-3000}, oversleep);
+ oversleep = std::min(int64_t{3000}, oversleep);
+ }
+}
+
+void AppAdapterSDL::DoPushMainThreadRunnable(Runnable* runnable) {
+ assert(sdl_runnable_event_id_ != 0);
+ SDL_Event event;
+ SDL_memset(&event, 0, sizeof(event));
+ event.type = sdl_runnable_event_id_;
+ event.user.code = 0;
+ event.user.data1 = runnable;
+ event.user.data2 = 0;
+ SDL_PushEvent(&event);
+}
+
+void AppAdapterSDL::DoExitMainThreadEventLoop() {
+ assert(g_core->InMainThread());
+ done_ = true;
+}
+
+void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) {
+ assert(g_core->InMainThread());
+ assert(g_base);
+
switch (event.type) {
case SDL_JOYAXISMOTION:
case SDL_JOYBUTTONDOWN:
@@ -37,8 +216,7 @@ void AppAdapterSDL::HandleSDLEvent(const SDL_Event& event) {
|| sdl_joysticks_[event.jbutton.which] == nullptr) {
return;
}
- JoystickInput* js = GetSDLJoystickInput_(&event);
- if (js) {
+ if (JoystickInput* js = GetSDLJoystickInput_(&event)) {
if (g_base) {
g_base->input->PushJoystickEvent(event, js);
}
@@ -53,11 +231,9 @@ void AppAdapterSDL::HandleSDLEvent(const SDL_Event& event) {
const SDL_MouseButtonEvent* e = &event.button;
// Convert sdl's coords to normalized view coords.
- float x = static_cast(e->x) / screen_dimensions_.x;
- float y = 1.0f - static_cast(e->y) / screen_dimensions_.y;
- if (g_base) {
- g_base->input->PushMouseDownEvent(e->button, Vector2f(x, y));
- }
+ float x = static_cast(e->x) / window_size_.x;
+ float y = 1.0f - static_cast(e->y) / window_size_.y;
+ g_base->input->PushMouseDownEvent(e->button, Vector2f(x, y));
break;
}
@@ -65,11 +241,9 @@ void AppAdapterSDL::HandleSDLEvent(const SDL_Event& event) {
const SDL_MouseButtonEvent* e = &event.button;
// Convert sdl's coords to normalized view coords.
- float x = static_cast(e->x) / screen_dimensions_.x;
- float y = 1.0f - static_cast(e->y) / screen_dimensions_.y;
- if (g_base) {
- g_base->input->PushMouseUpEvent(e->button, Vector2f(x, y));
- }
+ float x = static_cast(e->x) / window_size_.x;
+ float y = 1.0f - static_cast(e->y) / window_size_.y;
+ g_base->input->PushMouseUpEvent(e->button, Vector2f(x, y));
break;
}
@@ -77,500 +251,141 @@ void AppAdapterSDL::HandleSDLEvent(const SDL_Event& event) {
const SDL_MouseMotionEvent* e = &event.motion;
// Convert sdl's coords to normalized view coords.
- float x = static_cast(e->x) / screen_dimensions_.x;
- float y = 1.0f - static_cast(e->y) / screen_dimensions_.y;
- if (g_base) {
- g_base->input->PushMouseMotionEvent(Vector2f(x, y));
- }
+ float x = static_cast(e->x) / window_size_.x;
+ float y = 1.0f - static_cast(e->y) / window_size_.y;
+ g_base->input->PushMouseMotionEvent(Vector2f(x, y));
break;
}
case SDL_KEYDOWN: {
- if (g_base) {
- g_base->input->PushKeyPressEvent(event.key.keysym);
- }
+ g_base->input->PushKeyPressEvent(event.key.keysym);
break;
}
case SDL_KEYUP: {
- if (g_base) {
- g_base->input->PushKeyReleaseEvent(event.key.keysym);
- }
+ g_base->input->PushKeyReleaseEvent(event.key.keysym);
break;
}
-#if BA_SDL2_BUILD || BA_MINSDL_BUILD
case SDL_MOUSEWHEEL: {
const SDL_MouseWheelEvent* e = &event.wheel;
-
- // Seems in general scrolling is a lot faster on mac SDL compared to
- // windows/linux. (maybe its just for trackpads/etc..).. so lets
- // compensate.
- int scroll_speed;
- if (g_buildconfig.ostype_android()) {
- scroll_speed = 1;
- } else if (g_buildconfig.ostype_macos()) {
- scroll_speed = 500;
- } else {
- scroll_speed = 500;
- }
- if (g_base) {
- g_base->input->PushMouseScrollEvent(
- Vector2f(static_cast(e->x * scroll_speed),
- static_cast(e->y * scroll_speed)));
- }
+ int scroll_speed{500};
+ g_base->input->PushMouseScrollEvent(
+ Vector2f(static_cast(e->x * scroll_speed),
+ static_cast(e->y * scroll_speed)));
break;
}
-#endif // BA_SDL2_BUILD
-#if BA_OSTYPE_MACOS && BA_XCODE_BUILD
- case SDL_SMOOTHSCROLLEVENT: {
- const SDL_SmoothScrollEvent* e = &event.scroll;
- if (g_base) {
- g_base->input->PushSmoothMouseScrollEvent(
- Vector2f(0.2f * e->deltaX, -0.2f * e->deltaY), e->momentum);
- }
- break;
- }
-#endif
-
- // Currently used in our some of our heavily customized builds. Should
- // replace this with some sort of PushDrawEvent() thing.
-#if BA_XCODE_BUILD
- case SDL_RESIZEDRAWEVENT:
- case SDL_DRAWEVENT: {
- DrawFrame(event.type == SDL_RESIZEDRAWEVENT);
- break;
- }
-#endif // BA_OSTYPE_MACOS || BA_OSTYPE_ANDROID
-
- // Is there a reason we need to ignore these on ios?
- // do they even happen there?
- //
- // UPDATE: I think the even types are just not defined on our old iOS
- // SDL.
-#if BA_SDL2_BUILD && !BA_OSTYPE_IOS_TVOS && BA_ENABLE_SDL_JOYSTICKS
- case SDL_JOYDEVICEREMOVED:
- // In this case we're passed the instance-id of the joystick.
- SDLJoystickDisconnected_(event.jdevice.which);
- break;
case SDL_JOYDEVICEADDED:
- SDLJoystickConnected_(event.jdevice.which);
+ OnSDLJoystickAdded_(event.jdevice.which);
+ break;
+
+ case SDL_JOYDEVICEREMOVED:
+ OnSDLJoystickRemoved_(event.jdevice.which);
break;
-#endif
case SDL_QUIT:
- // g_base->logic->event_loop()->PushCall([] { g_base->logic->Shutdown();
- // });
g_base->logic->event_loop()->PushCall([] { g_base->ui->ConfirmQuit(); });
break;
-#if BA_OSTYPE_MACOS && BA_XCODE_BUILD && !BA_HEADLESS_BUILD
- case SDL_FULLSCREENSWITCH:
- // Our custom hacked-up SDL informs *us* when our window enters or
- // exits fullscreen. Let's commit this to our config so that we're in
- // sync..
- g_base->python->objs().PushCall(
- event.user.code ? BasePython::ObjID::kSetConfigFullscreenOnCall
- : BasePython::ObjID::kSetConfigFullscreenOffCall);
- g_base->graphics_server->set_fullscreen_enabled(event.user.code);
- break;
-#endif
-
-#if BA_SDL2_BUILD
-
case SDL_TEXTINPUT: {
- if (g_base) {
- g_base->input->PushTextInputEvent(event.text.text);
- }
+ g_base->input->PushTextInputEvent(event.text.text);
break;
}
case SDL_WINDOWEVENT: {
switch (event.window.event) {
- case SDL_WINDOWEVENT_MINIMIZED: // NOLINT(bugprone-branch-clone)
-
- // Hmm do we want to pause the app on desktop when minimized?
- // Gonna say no for now.
-#if BA_OSTYPE_IOS_TVOS
- PauseApp();
-#endif
+ case SDL_WINDOWEVENT_MAXIMIZED: {
+ printf("MAXIMIZED\n");
+ if (g_buildconfig.ostype_macos() && !fullscreen_) {
+ // Special case: on Mac, we wind up here if someone fullscreens
+ // our window via the window widget. This *basically* is the
+ // same thing as setting fullscreen through sdl, so we want to
+ // treat this as if we've changed the setting ourself. We write
+ // it to the config so that UIs can poll for it and pick up the
+ // change. We don't do this on other platforms where a maximized
+ // window is more distinctly different than a fullscreen one.
+ fullscreen_ = true;
+ g_base->logic->event_loop()->PushCall([] {
+ g_base->python->objs()
+ .Get(BasePython::ObjID::kSetConfigFullscreenOnCall)
+ .Call();
+ });
+ }
break;
+ }
case SDL_WINDOWEVENT_RESTORED:
-#if BA_OSTYPE_IOS_TVOS
- ResumeApp();
-#endif
+ printf("RESTORED\n");
+ if (g_buildconfig.ostype_macos() && fullscreen_) {
+ // See note above.
+ fullscreen_ = false;
+ g_base->logic->event_loop()->PushCall([] {
+ g_base->python->objs()
+ .Get(BasePython::ObjID::kSetConfigFullscreenOffCall)
+ .Call();
+ });
+ }
break;
- case SDL_WINDOWEVENT_RESIZED:
+ case SDL_WINDOWEVENT_MINIMIZED:
+ printf("MINIMIZED\n");
+ break;
+
+ case SDL_WINDOWEVENT_HIDDEN: {
+ // PauseApp();
+ printf("HIDDEN\n");
+ break;
+ }
+
+ case SDL_WINDOWEVENT_SHOWN: {
+ // ResumeApp();
+ printf("SHOWN\n");
+ break;
+ }
+
case SDL_WINDOWEVENT_SIZE_CHANGED: {
-#if BA_OSTYPE_IOS_TVOS
- // Do nothing here currently.
-#else // Generic SDL:
- int pixels_x, pixels_y;
- SDL_GL_GetDrawableSize(
- g_base->graphics_server->gl_context()->sdl_window(), &pixels_x,
- &pixels_y);
-
- // Pixel density is number of pixels divided by window dimension.
- screen_dimensions_ = Vector2f(event.window.data1, event.window.data2);
- g_base->graphics_server->SetScreenResolution(
- static_cast(pixels_x), static_cast(pixels_y));
-#endif // BA_OSTYPE_IOS_TVOS
-
+ // Note: this should cover all size changes; there is also
+ // SDL_WINDOWEVENT_RESIZED but according to the docs it only covers
+ // external events such as user window resizes.
+ UpdateScreenSizes_();
break;
}
default:
break;
}
break;
- default:
- break;
}
-#else // BA_SDL2_BUILD
- case SDL_VIDEORESIZE: {
- screen_dimensions_ = Vector2f(event.resize.w, event.resize.h);
- g_base->graphics_server->SetScreenResolution(event.resize.w,
- event.resize.h);
+
+ default: {
+ // Lastly handle our custom events (can't since their
+ // values are dynamic).
+ if (event.type == sdl_runnable_event_id_) {
+ auto* runnable = static_cast(event.user.data1);
+ runnable->RunAndLogErrors();
+ delete runnable;
+ return;
+ }
break;
}
-#endif // BA_SDL2_BUILD
}
}
-auto FilterSDLEvent(const SDL_Event* event) -> int {
- try {
- // If this event is coming from this thread, handle it immediately.
- if (std::this_thread::get_id() == g_core->main_thread_id) {
- auto app = static_cast_check_type(g_base->app_adapter);
- assert(app);
- if (app) {
- app->HandleSDLEvent(*event);
- }
- return false; // We handled it; sdl doesn't need to keep it.
- } else {
- // Otherwise just let SDL post it to the normal queue.. we process
- // this every now and then to pick these up.
- return true; // sdl should keep this.
- }
- } catch (const std::exception& e) {
- BA_LOG_ONCE(LogLevel::kError,
- std::string("Error in inline SDL-Event handling: ") + e.what());
- throw;
- }
+void AppAdapterSDL::OnSDLJoystickAdded_(int device_index) {
+ assert(g_base);
+ assert(g_core->InMainThread());
+
+ // Create the joystick here in the main thread and then pass it over to
+ // the logic thread to be added to the action.
+ auto* j = Object::NewDeferred(device_index);
+ int instance_id = SDL_JoystickInstanceID(j->sdl_joystick());
+ AddSDLInputDevice_(j, instance_id);
}
-#if BA_SDL2_BUILD
-inline auto FilterSDL2Event(void* user_data, SDL_Event* event) -> int {
- return FilterSDLEvent(event);
-}
-#endif
-
-// Note: can move this to AppAdapterSDL::AppAdapterSDL() once it is no longer
-// needed by the legacy mac build.
-void AppAdapterSDL::InitSDL() {
- assert(g_core);
-
- if (g_buildconfig.ostype_macos()) {
- // We don't want sdl to translate command/option clicks to different
- // mouse buttons dernit.
- g_core->platform->SetEnv("SDL_HAS3BUTTONMOUSE", "1");
- }
-
- // Let's turn on extra GL debugging on linux debug builds.
- if (g_buildconfig.ostype_linux() && g_buildconfig.debug_build()) {
- g_core->platform->SetEnv("MESA_DEBUG", "true");
- }
-
- uint32_t sdl_flags{};
-
- // We can skip joysticks and video for headless.
- if (!g_buildconfig.headless_build()) {
- sdl_flags |= SDL_INIT_VIDEO;
- if (explicit_bool(true)) {
- sdl_flags |= SDL_INIT_JOYSTICK;
-
- // KILL THIS ONCE MAC SDL1.2 BUILD IS DEAD.
- // Register our hotplug callbacks in our funky custom mac build.
-#if BA_OSTYPE_MACOS && BA_XCODE_BUILD && !BA_HEADLESS_BUILD
- SDL_JoystickSetHotPlugCallbacks(AppAdapterSDL::SDLJoystickConnected_,
- AppAdapterSDL::SDLJoystickDisconnected_);
-#endif
- }
- }
-
- // Whatever fancy-pants stuff SDL is trying to do with catching
- // signals/etc, we don't want it.
- sdl_flags |= SDL_INIT_NOPARACHUTE;
-
- // We may or may not want xinput on windows.
- if (g_buildconfig.ostype_windows()) {
- if (!g_core->platform->GetLowLevelConfigValue("enablexinput", 1)) {
- SDL_SetHint(SDL_HINT_XINPUT_ENABLED, "0");
- }
- }
-
- int result = SDL_Init(sdl_flags);
- if (result < 0) {
- throw Exception(std::string("SDL_Init failed: ") + SDL_GetError());
- }
-
- // KILL THIS ONCE SDL IS NO LONGER USED ON IOS BUILD
- if (g_buildconfig.ostype_ios_tvos() || g_buildconfig.ostype_android()) {
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
- SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
- }
-
- // KILL THIS ONCE MAC SDL 1.2 BUILD IS DEAD
-#if !BA_SDL2_BUILD
- SDL_EnableUNICODE(true);
- SDL_EnableKeyRepeat(200, 50);
-#endif
-}
-
-AppAdapterSDL::AppAdapterSDL() {
- InitSDL();
-
- // If we're not running our own even loop, we set up a filter to intercept
- // SDL events the moment they're generated and we process them
- // immediately. This way we don't have to poll for events and can be
- // purely callback-based, which fits in nicely with most modern event
- // models.
- if (!ManagesEventLoop()) {
-#if BA_SDL2_BUILD
- SDL_SetEventFilter(FilterSDL2Event, nullptr);
-#else
- SDL_SetEventFilter(FilterSDLEvent);
-#endif // BA_SDL2_BUILD
- } else {
- // Otherwise we do the standard old SDL polling stuff.
-
- // Set up a timer to chew through events every now and then. Polling
- // isn't super elegant, but is necessary in SDL's case. (SDLWaitEvent()
- // itself is pretty much a loop with SDL_PollEvents() followed by
- // SDL_Delay(10) until something is returned; In spirit, we're pretty
- // much doing that same thing, except that we're free to handle other
- // matters concurrently instead of being locked in a delay call.
- g_core->main_event_loop()->NewTimer(10, true, NewLambdaRunnable([this] {
- assert(g_base->app_adapter);
- g_base->app_adapter->RunEvents();
- }));
- }
-}
-
-void AppAdapterSDL::OnMainThreadStartApp() {
- AppAdapter::OnMainThreadStartApp();
-
- if (!g_core->HeadlessMode() && g_buildconfig.enable_sdl_joysticks()) {
- // Add initial sdl joysticks. any added/removed after this will be
- // handled via events. (it seems (on mac at least) even the initial ones
- // are handled via events, so lets make sure we handle redundant
- // joystick connections gracefully.
- if (explicit_bool(true)) {
- int joystick_count = SDL_NumJoysticks();
- for (int i = 0; i < joystick_count; i++) {
- AppAdapterSDL::SDLJoystickConnected_(i);
- }
-
- // We want events from joysticks.
- SDL_JoystickEventState(SDL_ENABLE);
- }
- }
-}
-
-void AppAdapterSDL::RunEvents() {
- AppAdapter::RunEvents();
-
- // Now run all pending SDL events until we run out or we're told to quit.
- SDL_Event event;
- while (SDL_PollEvent(&event) && (!g_core->main_event_loop()->done())) {
- HandleSDLEvent(event);
- }
-}
-
-void AppAdapterSDL::DidFinishRenderingFrame(FrameDef* frame) {
- AppAdapter::DidFinishRenderingFrame(frame);
- SwapBuffers_();
-}
-
-void AppAdapterSDL::DoSwap_() {
- assert(g_base->InGraphicsThread());
-
- if (g_buildconfig.debug_build()) {
- millisecs_t diff = g_core->GetAppTimeMillisecs() - swap_start_time_;
- if (diff > 5) {
- Log(LogLevel::kWarning, "Swap handling delay of " + std::to_string(diff));
- }
- }
-
-#if BA_ENABLE_OPENGL
-#if BA_SDL2_BUILD
- SDL_GL_SwapWindow(g_base->graphics_server->gl_context()->sdl_window());
-#else
- SDL_GL_SwapBuffers();
-#endif // BA_SDL2_BUILD
-#endif // BA_ENABLE_OPENGL
-
- millisecs_t cur_time = g_core->GetAppTimeMillisecs();
-
- // Do some post-render analysis/updates.
- if (last_swap_time_ != 0) {
- millisecs_t diff2 = cur_time - last_swap_time_;
- if (auto_vsync_) {
- UpdateAutoVSync_(static_cast(diff2));
- }
-
- // If we drop to a super-crappy FPS lets take some countermeasures such
- // as telling BG-dynamics to kill off some stuff.
- if (diff2 >= 1000 / 20) {
- too_slow_frame_count_++;
- } else {
- too_slow_frame_count_ = 0;
- }
-
- // Several slow frames in a row and we take action.
- if (too_slow_frame_count_ > 10) {
- too_slow_frame_count_ = 0;
-
- // A common cause of slowness is excessive smoke and bg stuff; lets
- // tell the bg dynamics thread to tone it down.
- g_base->bg_dynamics->TooSlow();
- }
- }
- last_swap_time_ = cur_time;
-}
-
-void AppAdapterSDL::SwapBuffers_() {
- swap_start_time_ = g_core->GetAppTimeMillisecs();
- assert(g_core->main_event_loop()->ThreadIsCurrent());
- DoSwap_();
-
- // FIXME: Move this somewhere reasonable. Not here. On mac/ios we wanna
- // delay our game-center login until we've drawn a few frames, so lets do
- // that here. ...hmm; why is that? I don't remember. Should revisit.
- if (g_buildconfig.use_game_center()) {
- static int f_count = 0;
- f_count++;
- if (f_count == 5) {
- g_core->platform->GameCenterLogin();
- }
- }
-}
-
-void AppAdapterSDL::UpdateAutoVSync_(int diff) {
- assert(auto_vsync_);
-
- // If we're currently vsyncing, watch for slow frames.
- if (vsync_enabled_) {
- bool should_disable{};
-
- // Note (March 2023): Currently mac opengl vsync seems broken on recent
- // OSs. See discussions: https://github.com/libsdl-org/SDL/issues/4918
- // Since Mac compositor generally avoids tearing anyway, just gonna have
- // 'auto' mode disable vsync for now. Explicit enable is still available
- // for odd cases where it still may be beneficial.
- if (g_buildconfig.ostype_macos()) {
- should_disable = true;
- } else {
- // Keep a smoothed average of the FPS we get with VSync on.
- {
- float this_fps = 1000.0f / static_cast(diff);
- float smoothing = 0.95f;
- average_vsync_fps_ =
- smoothing * average_vsync_fps_ + (1.0f - smoothing) * this_fps;
- }
-
- // FIXME: should not be assuming a 60fps framerate these days. If
- // framerate drops significantly below 60, flip vsync off to get a
- // better framerate (but *only* if we're pretty sure we can hit 60
- // with it on; otherwise if we're on a 30hz monitor we'll get into a
- // cycle of flipping it off and on repeatedly since we slow down a lot
- // with it on and then speed up a lot with it off).
- if (diff >= 1000 / 40 && average_vsync_fps_ > 55.0f) {
- vsync_bad_frame_count_++;
- } else {
- vsync_bad_frame_count_ = 0;
- }
- should_disable = vsync_bad_frame_count_ >= 10;
- }
- if (should_disable) {
- vsync_enabled_ = false;
-#if BA_ENABLE_OPENGL
- g_base->graphics_server->gl_context()->SetVSync(vsync_enabled_);
-#endif
- vsync_good_frame_count_ = 0;
- }
- } else {
- bool should_enable{};
- if (g_buildconfig.ostype_macos()) {
- should_enable = false;
- } else {
- // Vsync is currently off; watch for framerate staying consistently
- // high and then turn it on again.
- if (diff <= 1000 / 50) {
- vsync_good_frame_count_++;
- } else {
- vsync_good_frame_count_ = 0;
- }
- // FIXME - should not be assuming a 60fps framerate these days.
- should_enable = vsync_good_frame_count_ >= 60;
- }
- if (should_enable) {
- vsync_enabled_ = true;
-#if BA_ENABLE_OPENGL
- g_base->graphics_server->gl_context()->SetVSync(vsync_enabled_);
-#endif
- vsync_bad_frame_count_ = 0;
- }
- }
-}
-
-void AppAdapterSDL::SetAutoVSync(bool enable) {
- auto_vsync_ = enable;
- // If we're doing auto, start with vsync on.
- if (enable) {
- vsync_enabled_ = true;
-#if BA_ENABLE_OPENGL
- g_base->graphics_server->gl_context()->SetVSync(vsync_enabled_);
-#endif
- }
-}
-
-void AppAdapterSDL::SDLJoystickConnected_(int device_index) {
- assert(g_core && g_core->InMainThread());
-
- // We add all existing inputs when bootstrapping is complete; we should
- // never be getting these before that happens.
- if (!g_base) {
- Log(LogLevel::kError,
- "Unexpected SDLJoystickConnected_ early in boot sequence.");
- return;
- }
-
- // Create the joystick here in the main thread and then pass it over to the
- // logic thread to be added to the game.
- if (g_buildconfig.ostype_ios_tvos()) {
- BA_LOG_ONCE(LogLevel::kError, "WTF GOT SDL-JOY-CONNECTED ON IOS");
- } else {
- auto* j = Object::NewDeferred(device_index);
- if (g_buildconfig.sdl2_build() && g_buildconfig.enable_sdl_joysticks()) {
- int instance_id = SDL_JoystickInstanceID(j->sdl_joystick());
- Get()->AddSDLInputDevice_(j, instance_id);
- } else {
- Get()->AddSDLInputDevice_(j, device_index);
- }
- }
-}
-
-void AppAdapterSDL::SDLJoystickDisconnected_(int index) {
+void AppAdapterSDL::OnSDLJoystickRemoved_(int index) {
assert(g_core->InMainThread());
assert(index >= 0);
- Get()->RemoveSDLInputDevice_(index);
-}
-
-void AppAdapterSDL::SetInitialScreenDimensions(const Vector2f& dimensions) {
- screen_dimensions_ = dimensions;
+ RemoveSDLInputDevice_(index);
}
void AppAdapterSDL::AddSDLInputDevice_(JoystickInput* input, int index) {
@@ -579,7 +394,7 @@ void AppAdapterSDL::AddSDLInputDevice_(JoystickInput* input, int index) {
assert(g_core->InMainThread());
assert(index >= 0);
- // Keep a mapping of SDL input-device indices to Joysticks.
+ // Keep a mapping of SDL input-device indices to our Joysticks.
if (static_cast_check_fit(sdl_joysticks_.size()) <= index) {
sdl_joysticks_.resize(static_cast(index) + 1, nullptr);
}
@@ -640,6 +455,195 @@ auto AppAdapterSDL::GetSDLJoystickInput_(int sdl_joystick_id) const
return nullptr; // Epic fail.
}
+void AppAdapterSDL::SetScreen_(
+ bool fullscreen, int max_fps, VSyncRequest vsync_requested,
+ TextureQualityRequest texture_quality_requested,
+ GraphicsQualityRequest graphics_quality_requested) {
+ assert(g_base->InGraphicsThread());
+ assert(!g_core->HeadlessMode());
+
+ // If we know what we support, filter our request types to what is
+ // supported. This will keep us from rebuilding contexts if request type
+ // is flipping between different types that we don't support.
+ if (g_base->graphics->has_supports_high_quality_graphics_value()) {
+ if (!g_base->graphics->supports_high_quality_graphics()
+ && graphics_quality_requested > GraphicsQualityRequest::kMedium) {
+ graphics_quality_requested = GraphicsQualityRequest::kMedium;
+ }
+ }
+
+ bool do_toggle_fs{};
+ bool do_set_existing_fullscreen{};
+
+ auto* gs = g_base->graphics_server;
+
+ // We need a full renderer reload if quality values have changed
+ // or if we don't have one yet.
+ bool need_full_reload =
+ ((sdl_window_ == nullptr
+ || gs->texture_quality_requested() != texture_quality_requested)
+ || (gs->graphics_quality_requested() != graphics_quality_requested)
+ || !gs->texture_quality_set() || !gs->graphics_quality_set());
+
+ if (need_full_reload) {
+ ReloadRenderer_(fullscreen, graphics_quality_requested,
+ texture_quality_requested);
+ } else if (fullscreen != fullscreen_) {
+ SDL_SetWindowFullscreen(sdl_window_,
+ fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
+ fullscreen_ = fullscreen;
+ }
+
+ // VSync always gets set independent of the screen (though we set it down
+ // here to make sure we have one).
+ if (vsync_requested != vsync_) {
+ switch (vsync_requested) {
+ case VSyncRequest::kNever: {
+ SDL_GL_SetSwapInterval(0);
+ vsync_enabled_ = false;
+ break;
+ }
+ case VSyncRequest::kAlways: {
+ SDL_GL_SetSwapInterval(1);
+ vsync_enabled_ = true;
+ break;
+ }
+ case VSyncRequest::kAuto: {
+ // In this case, let's try setting to 'adaptive' and turn it off if
+ // that is unsupported.
+ auto result = SDL_GL_SetSwapInterval(-1);
+ if (result == 0) {
+ vsync_enabled_ = true;
+ } else {
+ SDL_GL_SetSwapInterval(0);
+ vsync_enabled_ = false;
+ }
+ break;
+ }
+ }
+ vsync_ = vsync_requested;
+ }
+
+ // This we can set anytime. Probably could have just set it from the logic
+ // thread where we read it, but let's be pedantic and keep everything to
+ // the main thread.
+ max_fps_ = max_fps;
+ // Allow -1 to mean no max.
+ if (max_fps_ != -1) {
+ max_fps_ = std::max(10, max_fps_);
+ max_fps_ = std::min(99999, max_fps_);
+ }
+
+ // Let the logic thread know we've got a graphics system up and running.
+ // It may use this cue to kick off asset loads and other bootstrapping.
+ g_base->logic->event_loop()->PushCall(
+ [] { g_base->logic->OnGraphicsReady(); });
+}
+
+void AppAdapterSDL::ReloadRenderer_(
+ bool fullscreen, GraphicsQualityRequest graphics_quality_requested,
+ TextureQualityRequest texture_quality_requested) {
+ assert(g_base->InGraphicsThread());
+
+ auto* gs = g_base->graphics_server;
+
+ if (gs->renderer() && gs->renderer_loaded()) {
+ gs->UnloadRenderer();
+ }
+
+ // If we don't haven't yet, create our window and renderer.
+ if (!sdl_window_) {
+ fullscreen_ = fullscreen;
+
+ // A reasonable default window size.
+ auto width = static_cast(kBaseVirtualResX * 0.8f);
+ auto height = static_cast(kBaseVirtualResY * 0.8f);
+
+ uint32_t flags = SDL_WINDOW_OPENGL
+ | SDL_WINDOW_SHOWN
+ // | SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS
+ | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE;
+ if (fullscreen) {
+ flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
+ }
+
+ // On Mac we ask for a GL 4.1 Core profile. This is supported by all
+ // hardware the game officially supports and is also the last version of
+ // GL supported on Apple hardware. So we know exactly what we have to
+ // work with.
+ int context_flags{};
+ if (g_buildconfig.ostype_macos()) {
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
+ SDL_GL_CONTEXT_PROFILE_CORE);
+ context_flags |= SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG;
+ } else {
+ // On other platforms, let's not ask for anything in particular.
+ // We'll work with whatever they give us if its 4.x or 3.x or we'll
+ // show a lovely error if it's older.
+
+ // Wondering if there would be a smarter strategy here; for example
+ // trying a few different specific core profiles.
+ }
+ if (g_buildconfig.debug_build()) {
+ context_flags |= SDL_GL_CONTEXT_DEBUG_FLAG;
+ }
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, context_flags);
+
+ sdl_window_ =
+ SDL_CreateWindow(nullptr, SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED, width, height, flags);
+ if (!sdl_window_) {
+ FatalError("Unable to create SDL Window of size " + std::to_string(width)
+ + " by " + std::to_string(height));
+ }
+ sdl_gl_context_ = SDL_GL_CreateContext(sdl_window_);
+ if (!sdl_gl_context_) {
+ FatalError("Unable to create SDL GL Context");
+ }
+ // auto result = SDL_GL_SetSwapInterval(-1);
+ // printf("GOT RESULT %d\n", result);
+
+ SDL_SetWindowTitle(sdl_window_, "BallisticaKit");
+
+ UpdateScreenSizes_();
+
+ // Now assign a GL renderer to the graphics-server to do its work.
+ assert(!gs->renderer());
+ if (!gs->renderer()) {
+ gs->set_renderer(new RendererGL());
+ }
+ }
+
+ // Update graphics quality based on request.
+ gs->set_graphics_quality_requested(graphics_quality_requested);
+ gs->set_texture_quality_requested(texture_quality_requested);
+
+ gs->LoadRenderer();
+}
+
+void AppAdapterSDL::UpdateScreenSizes_() {
+ assert(g_base->InGraphicsThread());
+
+ // Grab logical window dimensions (points?).
+ // This is the coordinate space SDL's events deal in.
+ int win_size_x, win_size_y;
+ SDL_GetWindowSize(sdl_window_, &win_size_x, &win_size_y);
+ window_size_ = Vector2f(win_size_x, win_size_y);
+
+ // Also grab the new size of the drawable; this is our physical
+ // (pixel) dimensions.
+ int pixels_x, pixels_y;
+ SDL_GL_GetDrawableSize(sdl_window_, &pixels_x, &pixels_y);
+ g_base->graphics_server->SetScreenResolution(static_cast(pixels_x),
+ static_cast(pixels_y));
+}
+
+auto AppAdapterSDL::CanToggleFullscreen() -> bool const { return true; }
+auto AppAdapterSDL::SupportsVSync() -> bool const { return true; }
+auto AppAdapterSDL::SupportsMaxFPS() -> bool const { return true; }
+
} // namespace ballistica::base
#endif // BA_SDL_BUILD
diff --git a/src/ballistica/base/app_adapter/app_adapter_sdl.h b/src/ballistica/base/app_adapter/app_adapter_sdl.h
index 7d2f567f..4c02e4b7 100644
--- a/src/ballistica/base/app_adapter/app_adapter_sdl.h
+++ b/src/ballistica/base/app_adapter/app_adapter_sdl.h
@@ -3,6 +3,7 @@
#ifndef BALLISTICA_BASE_APP_ADAPTER_APP_ADAPTER_SDL_H_
#define BALLISTICA_BASE_APP_ADAPTER_APP_ADAPTER_SDL_H_
+#include "ballistica/base/base.h"
#if BA_SDL_BUILD
#include
@@ -10,51 +11,83 @@
#include "ballistica/base/app_adapter/app_adapter.h"
#include "ballistica/shared/math/vector2f.h"
+// Predeclare for pointers.
+struct SDL_Window;
+
namespace ballistica::base {
class AppAdapterSDL : public AppAdapter {
public:
- static void InitSDL();
- AppAdapterSDL();
- void HandleSDLEvent(const SDL_Event& event);
- void RunEvents() override;
- void DidFinishRenderingFrame(FrameDef* frame) override;
- void SetAutoVSync(bool enable);
- void OnMainThreadStartApp() override;
-
- /// Return g_base->app_adapter as an AppAdapterSDL. (assumes it actually is
- /// one).
+ /// Return g_base->app_adapter as an AppAdapterSDL. (Assumes it actually
+ /// is one).
static AppAdapterSDL* Get() {
assert(g_base && g_base->app_adapter != nullptr);
assert(dynamic_cast(g_base->app_adapter)
== static_cast(g_base->app_adapter));
return static_cast(g_base->app_adapter);
}
- void SetInitialScreenDimensions(const Vector2f& dimensions);
+
+ AppAdapterSDL();
+
+ void OnMainThreadStartApp() override;
+ void DoApplyAppConfig() override;
+
+ auto CanToggleFullscreen() -> bool const override;
+ auto SupportsVSync() -> bool const override;
+ auto SupportsMaxFPS() -> bool const override;
+
+ protected:
+ void DoPushMainThreadRunnable(Runnable* runnable) override;
+ void RunMainThreadEventLoopToCompletion() override;
+ void DoExitMainThreadEventLoop() override;
private:
- static void SDLJoystickConnected_(int index);
- static void SDLJoystickDisconnected_(int index);
+ void SetScreen_(bool fullscreen, int max_fps, VSyncRequest vsync_requested,
+ TextureQualityRequest texture_quality_requested,
+ GraphicsQualityRequest graphics_quality_requested);
+ void HandleSDLEvent_(const SDL_Event& event);
+ void UpdateScreenSizes_();
+ void ReloadRenderer_(bool fullscreen,
+ GraphicsQualityRequest graphics_quality_requested,
+ TextureQualityRequest texture_quality_requested);
+ void OnSDLJoystickAdded_(int index);
+ void OnSDLJoystickRemoved_(int index);
// Given an SDL joystick ID, returns our Ballistica input for it.
auto GetSDLJoystickInput_(int sdl_joystick_id) const -> JoystickInput*;
// The same but using sdl events.
auto GetSDLJoystickInput_(const SDL_Event* e) const -> JoystickInput*;
- void DoSwap_();
- void SwapBuffers_();
- void UpdateAutoVSync_(int diff);
+ // void DoSwap_();
+ // void SwapBuffers_();
+ // void UpdateAutoVSync_(int diff);
void AddSDLInputDevice_(JoystickInput* input, int index);
void RemoveSDLInputDevice_(int index);
- millisecs_t last_swap_time_{};
- millisecs_t swap_start_time_{};
- int too_slow_frame_count_{};
- bool auto_vsync_{};
- bool vsync_enabled_{true};
- float average_vsync_fps_{60.0f};
- int vsync_good_frame_count_{};
- int vsync_bad_frame_count_{};
+ // millisecs_t last_swap_time_{};
+ // millisecs_t swap_start_time_{};
+ // int too_slow_frame_count_{};
+ // bool auto_vsync_{};
+ // bool vsync_enabled_{true};
+ // float average_vsync_fps_{60.0f};
+ // int vsync_good_frame_count_{};
+ // int vsync_bad_frame_count_{};
+ uint32_t sdl_runnable_event_id_{};
std::vector sdl_joysticks_;
/// This is in points, not pixels.
- Vector2f screen_dimensions_{1.0f, 1.0f};
+ Vector2f window_size_{1.0f, 1.0f};
+ SDL_Window* sdl_window_{};
+ void* sdl_gl_context_{};
+ // SDL_Surface* sdl_screen_surface_{};
+ bool done_{};
+ bool fullscreen_{};
+ VSyncRequest vsync_{VSyncRequest::kNever};
+ bool vsync_enabled_{};
+ microsecs_t oversleep{};
+ int max_fps_{60};
+ bool debug_log_sdl_frame_timing_{};
+ // std::unique_ptr gl_context_;
+
+ // TEMP
+ // friend class GLContext;
+ friend class GraphicsServer;
};
} // namespace ballistica::base
diff --git a/src/ballistica/base/app_adapter/app_adapter_vr.cc b/src/ballistica/base/app_adapter/app_adapter_vr.cc
index af0b74da..57ee5322 100644
--- a/src/ballistica/base/app_adapter/app_adapter_vr.cc
+++ b/src/ballistica/base/app_adapter/app_adapter_vr.cc
@@ -13,9 +13,11 @@ namespace ballistica::base {
AppAdapterVR::AppAdapterVR() {}
+auto AppAdapterVR::ManagesMainThreadEventLoop() const -> bool { return false; }
+
void AppAdapterVR::PushVRSimpleRemoteStateCall(
const VRSimpleRemoteState& state) {
- g_core->main_event_loop()->PushCall([this, state] {
+ g_base->app_adapter->PushMainThreadCall([this, state] {
// Convert this to a full hands state, adding in some simple elbow
// positioning of our own and left/right.
VRHandsState s;
@@ -47,16 +49,19 @@ void AppAdapterVR::VRPreDraw() {
return;
}
assert(g_base->InGraphicsThread());
- if (FrameDef* frame_def = g_base->graphics_server->GetRenderFrameDef()) {
- // Note: this could be part of PreprocessRenderFrameDef but the non-vr
- // path needs it to be separate since preprocess doesn't happen
- // sometimes. Should probably clean that up.
- g_base->graphics_server->RunFrameDefMeshUpdates(frame_def);
+ // FIXME - this is internal graphics-server details that the render-server
+ // should handle.
+ Log(LogLevel::kWarning, "FIXME: Have GraphicsServer handle VR drawing.");
+ // if (FrameDef* frame_def = g_base->graphics_server->GetRenderFrameDef()) {
+ // // Note: this could be part of PreprocessRenderFrameDef but the non-vr
+ // // path needs it to be separate since preprocess doesn't happen
+ // // sometimes. Should probably clean that up.
+ // g_base->graphics_server->RunFrameDefMeshUpdates(frame_def);
- // store this for the duration of this frame
- vr_render_frame_def_ = frame_def;
- g_base->graphics_server->PreprocessRenderFrameDef(frame_def);
- }
+ // // store this for the duration of this frame
+ // vr_render_frame_def_ = frame_def;
+ // g_base->graphics_server->PreprocessRenderFrameDef(frame_def);
+ // }
}
void AppAdapterVR::VRPostDraw() {
@@ -69,7 +74,8 @@ void AppAdapterVR::VRPostDraw() {
g_base->graphics_server->FinishRenderFrameDef(vr_render_frame_def_);
vr_render_frame_def_ = nullptr;
}
- RunRenderUpkeepCycle();
+ Log(LogLevel::kWarning, "WOULD RUN RENDER UPKEEP CYCLE");
+ // RunRenderUpkeepCycle();
}
void AppAdapterVR::VRSetHead(float tx, float ty, float tz, float yaw,
@@ -118,6 +124,22 @@ void AppAdapterVR::VRDrawEye(int eye, float yaw, float pitch, float roll,
}
}
+void AppAdapterVR::RunMainThreadEventLoopToCompletion() {
+ // FIXME - can basically copy sdl path here methinks.
+ FatalError(
+ "FIXME: IMPLEMENT AppAdapterVR::RunMainThreadEventLoopToCompletion");
+ // g_core->main_event_loop()->RunToCompletion();
+}
+
+void AppAdapterVR::DoPushMainThreadRunnable(Runnable* runnable) {
+ FatalError("FIXME: DoPushMainThreadRunnable unimplemented here.");
+}
+
+void AppAdapterVR::DoExitMainThreadEventLoop() {
+ FatalError("FIXME: IMPLEMENT AppAdapterVR::DoExitMainThreadEventLoop");
+ // g_core->main_event_loop()->Exit();
+}
+
} // namespace ballistica::base
#endif // BA_VR_BUILD
diff --git a/src/ballistica/base/app_adapter/app_adapter_vr.h b/src/ballistica/base/app_adapter/app_adapter_vr.h
index 4a62dbe3..8e445e0e 100644
--- a/src/ballistica/base/app_adapter/app_adapter_vr.h
+++ b/src/ballistica/base/app_adapter/app_adapter_vr.h
@@ -19,8 +19,10 @@ class AppAdapterVR : public AppAdapter {
float r2 = 0.0f;
};
+ auto ManagesMainThreadEventLoop() const -> bool override;
+
/// Return g_app as a AppAdapterVR. (assumes it actually is one).
- static auto get() -> AppAdapterVR* {
+ static auto Get() -> AppAdapterVR* {
assert(g_base != nullptr && g_base->app_adapter != nullptr);
assert(dynamic_cast(g_base->app_adapter)
== static_cast(g_base->app_adapter));
@@ -39,6 +41,11 @@ class AppAdapterVR : public AppAdapter {
float tan_r, float tan_b, float tan_t, float eye_x,
float eye_y, float eye_z, int viewport_x, int viewport_y);
+ protected:
+ void DoPushMainThreadRunnable(Runnable* runnable) override;
+ void RunMainThreadEventLoopToCompletion() override;
+ void DoExitMainThreadEventLoop() override;
+
private:
FrameDef* vr_render_frame_def_{};
};
diff --git a/src/ballistica/base/app_mode/app_mode.h b/src/ballistica/base/app_mode/app_mode.h
index 4aa13b40..b98fa2a4 100644
--- a/src/ballistica/base/app_mode/app_mode.h
+++ b/src/ballistica/base/app_mode/app_mode.h
@@ -19,9 +19,9 @@ const microsecs_t kAppModeMaxHeadlessDisplayStep{500000};
const microsecs_t kAppModeMinHeadlessDisplayStep{1000};
/// Represents 'what the app is doing'. The global app-mode can be switched
-/// as the app is running. Be aware that, unlike the AppAdapter classes
-/// which primarily deal with the main thread, most functionality here is
-/// based in the logic thread.
+/// as the app is running. The Python layer has its own Python AppMode
+/// classes, and generally when one of them becomes active it calls down
+/// to the C++ layer to make a corresponding C++ AppMode class active.
class AppMode {
public:
AppMode();
@@ -33,13 +33,12 @@ class AppMode {
/// Called just before the app-mode ceases being the active one.
virtual void OnDeactivate();
+ /// Logic thread callbacks that run while the app-mode is active.
virtual void OnAppStart();
virtual void OnAppPause();
virtual void OnAppResume();
virtual void OnAppShutdown();
virtual void OnAppShutdownComplete();
-
- /// Apply the app config.
virtual void DoApplyAppConfig();
/// Update the logic thread for a new display-time. Can be called at any
diff --git a/src/ballistica/base/app_mode/app_mode_empty.cc b/src/ballistica/base/app_mode/app_mode_empty.cc
index 5373698a..6f409f28 100644
--- a/src/ballistica/base/app_mode/app_mode_empty.cc
+++ b/src/ballistica/base/app_mode/app_mode_empty.cc
@@ -41,29 +41,29 @@ void AppModeEmpty::DrawWorld(base::FrameDef* frame_def) {
}
auto& grp(*hello_text_group_);
auto* pass = frame_def->overlay_pass();
+
SimpleComponent c(pass);
c.SetTransparent(true);
c.SetColor(0.7f, 0.0f, 1.0f, 1.0f);
- c.PushTransform();
+ {
+ auto xf = c.ScopedTransform();
+ auto xoffs =
+ sinf(static_cast(frame_def->display_time_millisecs()) / 600.0f);
+ auto yoffs =
+ cosf(static_cast(frame_def->display_time_millisecs()) / 600.0f);
- auto xoffs =
- sinf(static_cast(frame_def->display_time_millisecs()) / 600.0f);
- auto yoffs =
- cosf(static_cast(frame_def->display_time_millisecs()) / 600.0f);
+ // Z value -1 will draw us under most everything.
+ c.Translate(pass->virtual_width() * 0.5f - 70.0f + xoffs * 200.0f,
+ pass->virtual_height() * 0.5f - 20.0f + yoffs * 200.0f, -1.0f);
+ c.Scale(2.0, 2.0);
- // Z value -1 will draw us under most everything.
- c.Translate(pass->virtual_width() * 0.5f - 70.0f + xoffs * 200.0f,
- pass->virtual_height() * 0.5f - 20.0f + yoffs * 200.0f, -1.0f);
- c.Scale(2.0, 2.0);
-
- int text_elem_count = grp.GetElementCount();
- for (int e = 0; e < text_elem_count; e++) {
- c.SetTexture(grp.GetElementTexture(e));
- c.SetFlatness(1.0f);
- c.DrawMesh(grp.GetElementMesh(e));
+ int text_elem_count = grp.GetElementCount();
+ for (int e = 0; e < text_elem_count; e++) {
+ c.SetTexture(grp.GetElementTexture(e));
+ c.SetFlatness(1.0f);
+ c.DrawMesh(grp.GetElementMesh(e));
+ }
}
-
- c.PopTransform();
c.Submit();
}
diff --git a/src/ballistica/base/assets/texture_asset.cc b/src/ballistica/base/assets/texture_asset.cc
index 0edbdad3..f71b3af6 100644
--- a/src/ballistica/base/assets/texture_asset.cc
+++ b/src/ballistica/base/assets/texture_asset.cc
@@ -41,6 +41,7 @@ static void rgba8888_unpremultiply_in_place(uint8_t* src, size_t cb) {
}
TextureAsset::TextureAsset() = default;
+
TextureAsset::TextureAsset(const std::string& file_in, TextureType type_in,
TextureMinQuality min_quality_in)
: file_name_(file_in), type_(type_in), min_quality_(min_quality_in) {
@@ -59,19 +60,19 @@ TextureAsset::TextureAsset(TextPacker* packer) : packer_(packer) {
}
TextureAsset::TextureAsset(const std::string& qr_url) : is_qr_code_(true) {
- int hard_limit{96};
- int soft_limit{64};
+ size_t hard_limit{96};
+ size_t soft_limit{64};
if (qr_url.size() > hard_limit) {
char buffer[512];
snprintf(buffer, sizeof(buffer),
- "QR code url byte length %zu exceeds hard-limit of %d;"
+ "QR code url byte length %zu exceeds hard-limit of %zu;"
" please use shorter urls. (url=%s)",
qr_url.size(), hard_limit, qr_url.c_str());
throw Exception(buffer, PyExcType::kValue);
} else if (qr_url.size() > soft_limit) {
char buffer[512];
snprintf(buffer, sizeof(buffer),
- "QR code url byte length %zu exceeds soft-limit of %d;"
+ "QR code url byte length %zu exceeds soft-limit of %zu;"
" please use shorter urls. (url=%s)",
qr_url.size(), soft_limit, qr_url.c_str());
Log(LogLevel::kWarning, buffer);
@@ -211,15 +212,11 @@ void TextureAsset::DoPreload() {
if (file_name_size > 12
&& !strcmp(file_name_full_.c_str() + file_name_size - 12,
".android_dds")) {
-#if BA_ENABLE_OPENGL
LoadDDS(file_name_full_, preload_datas_[0].buffers,
preload_datas_[0].widths, preload_datas_[0].heights,
preload_datas_[0].formats, preload_datas_[0].sizes,
texture_quality, static_cast(min_quality_),
&preload_datas_[0].base_level);
-#else
- throw Exception();
-#endif
// We should only be loading this if we support etc1 in hardware.
assert(g_base->graphics_server->SupportsTextureCompressionType(
@@ -238,15 +235,11 @@ void TextureAsset::DoPreload() {
} else if (!strcmp(file_name_full_.c_str() + file_name_size - 4,
".dds")) {
// Dxt1 for non-alpha and dxt5 for alpha (.dds files).
-#if BA_ENABLE_OPENGL
LoadDDS(file_name_full_, preload_datas_[0].buffers,
preload_datas_[0].widths, preload_datas_[0].heights,
preload_datas_[0].formats, preload_datas_[0].sizes,
texture_quality, static_cast(min_quality_),
&preload_datas_[0].base_level);
-#else
- throw Exception();
-#endif
// Decompress dxt1/dxt5 if we don't natively support it.
if (!g_base->graphics_server->SupportsTextureCompressionType(
@@ -257,15 +250,11 @@ void TextureAsset::DoPreload() {
".ktx")) {
// Etc2 or etc1 for non-alpha and etc2 for alpha (.ktx files).
try {
-#if BA_ENABLE_OPENGL
LoadKTX(file_name_full_, preload_datas_[0].buffers,
preload_datas_[0].widths, preload_datas_[0].heights,
preload_datas_[0].formats, preload_datas_[0].sizes,
texture_quality, static_cast(min_quality_),
&preload_datas_[0].base_level);
-#else
- throw Exception();
-#endif
} catch (const std::exception& e) {
throw Exception("Error loading file '" + file_name_full_
+ "': " + e.what());
@@ -343,15 +332,11 @@ void TextureAsset::DoPreload() {
&& !strcmp(file_name_full_.c_str() + file_name_size - 12,
".android_dds")) {
try {
-#if BA_ENABLE_OPENGL
LoadDDS(name, preload_datas_[d].buffers, preload_datas_[d].widths,
preload_datas_[d].heights, preload_datas_[d].formats,
preload_datas_[d].sizes, texture_quality,
static_cast(min_quality_),
&preload_datas_[d].base_level);
-#else
- throw Exception();
-#endif
} catch (const std::exception& e) {
throw Exception("Error loading file '" + file_name_full_
+ "': " + e.what());
@@ -373,16 +358,12 @@ void TextureAsset::DoPreload() {
}
} else if (!strcmp(file_name_full_.c_str() + file_name_size - 4,
".dds")) {
-#if BA_ENABLE_OPENGL
// Dxt1 for non-alpha and dxt5 for alpha (.dds files).
LoadDDS(name, preload_datas_[d].buffers, preload_datas_[d].widths,
preload_datas_[d].heights, preload_datas_[d].formats,
preload_datas_[d].sizes, texture_quality,
static_cast(min_quality_),
&preload_datas_[d].base_level);
-#else
- throw Exception();
-#endif
// Decompress dxt1/dxt5 if we don't natively support it.
if (!g_base->graphics_server->SupportsTextureCompressionType(
@@ -392,15 +373,11 @@ void TextureAsset::DoPreload() {
} else if (!strcmp(file_name_full_.c_str() + file_name_size - 4,
".ktx")) {
// Etc2 or etc1 for non-alpha and etc2 for alpha (.ktx files)
-#if BA_ENABLE_OPENGL
LoadKTX(name, preload_datas_[d].buffers, preload_datas_[d].widths,
preload_datas_[d].heights, preload_datas_[d].formats,
preload_datas_[d].sizes, texture_quality,
static_cast(min_quality_),
&preload_datas_[d].base_level);
-#else
- throw Exception();
-#endif
// Decompress etc2 ones if we don't natively support them.
if (((preload_datas_[d].formats[preload_datas_[d].base_level]
diff --git a/src/ballistica/base/assets/texture_asset_preload_data.h b/src/ballistica/base/assets/texture_asset_preload_data.h
index db251a52..280a6767 100644
--- a/src/ballistica/base/assets/texture_asset_preload_data.h
+++ b/src/ballistica/base/assets/texture_asset_preload_data.h
@@ -12,7 +12,8 @@ namespace ballistica::base {
const int kMaxTextureLevels = 14;
// Temporary data that is passed along to the renderer when creating
-// rendererdata. This may include sdl surfaces and/or compressed buffers.
+// renderer-data. This may include things like sdl surfaces and/or
+// compressed buffers.
class TextureAssetPreloadData {
public:
static void rgba8888_to_rgba4444_in_place(void* src, size_t cb);
diff --git a/src/ballistica/base/assets/texture_asset_renderer_data.h b/src/ballistica/base/assets/texture_asset_renderer_data.h
index 575bc14c..85f75915 100644
--- a/src/ballistica/base/assets/texture_asset_renderer_data.h
+++ b/src/ballistica/base/assets/texture_asset_renderer_data.h
@@ -7,8 +7,7 @@
namespace ballistica::base {
-// Renderer-specific data (gl tex, etc)
-// this is extended by the renderer
+// Renderer-specific data (gl tex, etc). To be extended by the renderer.
class TextureAssetRendererData : public Object {
public:
auto GetDefaultOwnerThread() const -> EventLoopID override {
@@ -18,9 +17,7 @@ class TextureAssetRendererData : public Object {
// Create the renderer data but don't load it in yet.
TextureAssetRendererData() = default;
- // load the data.
- // if incremental is true, return whether the load was completed
- // (non-incremental loads should always complete)
+ // Load the data.
virtual void Load() = 0;
};
diff --git a/src/ballistica/base/audio/al_sys.h b/src/ballistica/base/audio/al_sys.h
index f1d017dd..ad69ffd3 100644
--- a/src/ballistica/base/audio/al_sys.h
+++ b/src/ballistica/base/audio/al_sys.h
@@ -9,7 +9,7 @@
#if BA_ENABLE_AUDIO
-#if HAVE_FRAMEWORK_OPENAL
+#if BA_HAVE_FRAMEWORK_OPENAL
#include
#include
#else
diff --git a/src/ballistica/base/audio/audio_server.cc b/src/ballistica/base/audio/audio_server.cc
index 03d9178f..aef706c9 100644
--- a/src/ballistica/base/audio/audio_server.cc
+++ b/src/ballistica/base/audio/audio_server.cc
@@ -33,7 +33,9 @@ LPALCDEVICERESUMESOFT alcDeviceResumeSOFT;
const int kAudioProcessIntervalNormal{500};
const int kAudioProcessIntervalFade{50};
const int kAudioProcessIntervalPendingLoad{1};
+#if (BA_DEBUG_BUILD || BA_TEST_BUILD)
const bool kShowInUseSounds{};
+#endif
int AudioServer::al_source_count_ = 0;
diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc
index c66ee2a8..da18bcec 100644
--- a/src/ballistica/base/base.cc
+++ b/src/ballistica/base/base.cc
@@ -38,7 +38,7 @@ core::CoreFeatureSet* g_core{};
BaseFeatureSet* g_base{};
BaseFeatureSet::BaseFeatureSet()
- : app_adapter{BasePlatform::CreateAppAdapter()},
+ : app_adapter{AppAdapter::Create()},
app_config{new AppConfig()},
app_mode_{AppModeEmpty::GetSingleton()},
assets{new Assets()},
@@ -50,7 +50,7 @@ BaseFeatureSet::BaseFeatureSet()
bg_dynamics_server{g_core->HeadlessMode() ? nullptr
: new BGDynamicsServer},
context_ref{new ContextRef(nullptr)},
- graphics{BasePlatform::CreateGraphics()},
+ graphics{Graphics::Create()},
graphics_server{new GraphicsServer()},
huffman{new Huffman()},
input{new Input()},
@@ -58,7 +58,7 @@ BaseFeatureSet::BaseFeatureSet()
network_reader{new NetworkReader()},
network_writer{new NetworkWriter()},
networking{new Networking()},
- platform{BasePlatform::CreatePlatform()},
+ platform{BasePlatform::Create()},
python{new BasePython()},
stdio_console{g_buildconfig.enable_stdio_console() ? new StdioConsole()
: nullptr},
@@ -223,8 +223,8 @@ void BaseFeatureSet::OnAppShutdownComplete() {
g_core->LifecycleLog("app exiting (main thread)");
// Flag our own event loop to exit (or ask the OS to if they're managing).
- if (app_adapter->ManagesEventLoop()) {
- g_core->main_event_loop()->Quit();
+ if (app_adapter->ManagesMainThreadEventLoop()) {
+ app_adapter->DoExitMainThreadEventLoop();
} else {
platform->TerminateApp();
}
@@ -276,14 +276,14 @@ void BaseFeatureSet::set_app_mode(AppMode* mode) {
}
}
-auto BaseFeatureSet::AppManagesEventLoop() -> bool {
- return app_adapter->ManagesEventLoop();
+auto BaseFeatureSet::AppManagesMainThreadEventLoop() -> bool {
+ return app_adapter->ManagesMainThreadEventLoop();
}
void BaseFeatureSet::RunAppToCompletion() {
BA_PRECONDITION(g_core->InMainThread());
BA_PRECONDITION(g_base);
- BA_PRECONDITION(g_base->app_adapter->ManagesEventLoop());
+ BA_PRECONDITION(g_base->app_adapter->ManagesMainThreadEventLoop());
BA_PRECONDITION(!called_run_app_to_completion_);
called_run_app_to_completion_ = true;
@@ -294,12 +294,12 @@ void BaseFeatureSet::RunAppToCompletion() {
// Let go of the GIL while we're running.
Python::ScopedInterpreterLockRelease gil_release;
- g_core->main_event_loop()->RunToCompletion();
+ g_base->app_adapter->RunMainThreadEventLoopToCompletion();
}
-void BaseFeatureSet::PrimeAppMainThreadEventPump() {
- app_adapter->PrimeMainThreadEventPump();
-}
+// void BaseFeatureSet::PrimeAppMainThreadEventPump() {
+// app_adapter->PrimeMainThreadEventPump();
+// }
auto BaseFeatureSet::HavePlus() -> bool {
if (!plus_soft_ && !tried_importing_plus_) {
@@ -469,10 +469,12 @@ auto BaseFeatureSet::InLogicThread() const -> bool {
}
auto BaseFeatureSet::InGraphicsThread() const -> bool {
- if (auto* loop = graphics_server->event_loop()) {
- return loop->ThreadIsCurrent();
- }
- return false;
+ // FIXME.
+ return g_core->InMainThread();
+ // if (auto* loop = graphics_server->event_loop()) {
+ // return loop->ThreadIsCurrent();
+ // }
+ // return false;
}
auto BaseFeatureSet::InAudioThread() const -> bool {
diff --git a/src/ballistica/base/base.h b/src/ballistica/base/base.h
index 173ed382..673e639f 100644
--- a/src/ballistica/base/base.h
+++ b/src/ballistica/base/base.h
@@ -14,7 +14,6 @@
// It predeclares our feature-set's various types and globals and other
// bits.
-// Predeclared types from other feature sets that we use.
namespace ballistica::core {
class CoreConfig;
class CoreFeatureSet;
@@ -58,7 +57,6 @@ class Context;
class ContextRef;
class DataAsset;
class FrameDef;
-class GLContext;
class Graphics;
class GraphicsServer;
class Huffman;
@@ -177,6 +175,8 @@ enum class GraphicsQuality {
kHigher,
};
+enum class VSyncRequest { kNever, kAlways, kAuto };
+
/// Requests for exact or auto graphics quality values.
enum class GraphicsQualityRequest {
kUnset,
@@ -624,13 +624,13 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
/// Called when app shutdown process completes. Sets app to exit.
void OnAppShutdownComplete();
- auto AppManagesEventLoop() -> bool override;
+ auto AppManagesMainThreadEventLoop() -> bool override;
/// Run app event loop to completion (only applies to flavors which manage
/// their own event loop).
void RunAppToCompletion() override;
- void PrimeAppMainThreadEventPump() override;
+ // void PrimeAppMainThreadEventPump() override;
auto CurrentContext() -> const ContextRef& {
assert(InLogicThread()); // Up to caller to ensure this.
diff --git a/src/ballistica/base/dynamics/bg/bg_dynamics.cc b/src/ballistica/base/dynamics/bg/bg_dynamics.cc
index 4609b6c8..7bc17a3b 100644
--- a/src/ballistica/base/dynamics/bg/bg_dynamics.cc
+++ b/src/ballistica/base/dynamics/bg/bg_dynamics.cc
@@ -203,7 +203,9 @@ void BGDynamics::Draw(FrameDef* frame_def) {
// Draw shadows.
if (ds->shadow_vertices.Exists()) {
assert(ds->shadow_indices.Exists());
- if (!shadows_mesh_.Exists()) shadows_mesh_ = Object::New();
+ if (!shadows_mesh_.Exists()) {
+ shadows_mesh_ = Object::New();
+ }
shadows_mesh_->SetIndexData(ds->shadow_indices);
shadows_mesh_->SetData(
Object::Ref>(ds->shadow_vertices));
diff --git a/src/ballistica/base/dynamics/collision_cache.cc b/src/ballistica/base/dynamics/collision_cache.cc
index a063f743..5f26c1fb 100644
--- a/src/ballistica/base/dynamics/collision_cache.cc
+++ b/src/ballistica/base/dynamics/collision_cache.cc
@@ -31,46 +31,50 @@ void CollisionCache::Draw(FrameDef* frame_def) {
c.SetColor(0, 1, 0, 0.1f);
float cell_width = (1.0f / static_cast(grid_width_));
float cell_height = (1.0f / static_cast(grid_height_));
- c.PushTransform();
- c.Translate((x_min_ + x_max_) * 0.5f, 0, (z_min_ + z_max_) * 0.5f);
- c.Scale(x_max_ - x_min_, 1, z_max_ - z_min_);
- c.PushTransform();
- c.Scale(1, 0.01f, 1);
- c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kBox));
- c.PopTransform();
- c.Translate(-0.5f + 0.5f * cell_width, 0, -0.5f + 0.5f * cell_height);
- for (int x = 0; x < grid_width_; x++) {
- for (int z = 0; z < grid_height_; z++) {
- int cell_index = z * grid_width_ + x;
- assert(cell_index >= 0 && cell_index < static_cast(glow_.size()));
- if (glow_[cell_index]) {
- c.SetColor(1, 1, 1, 0.2f);
- } else {
- c.SetColor(0, 0, 1, 0.2f);
- }
- c.PushTransform();
- c.Translate(static_cast(x) / static_cast(grid_width_),
- cells_[cell_index].height_confirmed_collide_,
- static_cast(z) / static_cast(grid_height_));
- c.Scale(0.95f * cell_width, 0.01f, 0.95f * cell_height);
+ {
+ auto xf = c.ScopedTransform();
+ c.Translate((x_min_ + x_max_) * 0.5f, 0, (z_min_ + z_max_) * 0.5f);
+ c.Scale(x_max_ - x_min_, 1, z_max_ - z_min_);
+ {
+ auto xf = c.ScopedTransform();
+ c.Scale(1, 0.01f, 1);
c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kBox));
- c.PopTransform();
- if (glow_[cell_index]) {
- c.SetColor(1, 1, 1, 0.2f);
- } else {
- c.SetColor(1, 0, 0, 0.2f);
+ }
+ c.Translate(-0.5f + 0.5f * cell_width, 0, -0.5f + 0.5f * cell_height);
+ for (int x = 0; x < grid_width_; x++) {
+ for (int z = 0; z < grid_height_; z++) {
+ int cell_index = z * grid_width_ + x;
+ assert(cell_index >= 0 && cell_index < static_cast(glow_.size()));
+ if (glow_[cell_index]) {
+ c.SetColor(1, 1, 1, 0.2f);
+ } else {
+ c.SetColor(0, 0, 1, 0.2f);
+ }
+ {
+ auto xf = c.ScopedTransform();
+ c.Translate(static_cast(x) / static_cast(grid_width_),
+ cells_[cell_index].height_confirmed_collide_,
+ static_cast(z) / static_cast(grid_height_));
+ c.Scale(0.95f * cell_width, 0.01f, 0.95f * cell_height);
+ c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kBox));
+ }
+ if (glow_[cell_index]) {
+ c.SetColor(1, 1, 1, 0.2f);
+ } else {
+ c.SetColor(1, 0, 0, 0.2f);
+ }
+ {
+ auto xf = c.ScopedTransform();
+ c.Translate(static_cast(x) / static_cast(grid_width_),
+ cells_[cell_index].height_confirmed_empty_,
+ static_cast(z) / static_cast(grid_height_));
+ c.Scale(0.95f * cell_width, 0.01f, 0.95f * cell_height);
+ c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kBox));
+ }
+ glow_[cell_index] = 0;
}
- c.PushTransform();
- c.Translate(static_cast(x) / static_cast(grid_width_),
- cells_[cell_index].height_confirmed_empty_,
- static_cast(z) / static_cast(grid_height_));
- c.Scale(0.95f * cell_width, 0.01f, 0.95f * cell_height);
- c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kBox));
- c.PopTransform();
- glow_[cell_index] = 0;
}
}
- c.PopTransform();
c.Submit();
if (explicit_bool(false)) {
@@ -79,47 +83,54 @@ void CollisionCache::Draw(FrameDef* frame_def) {
c2.SetColor(1, 0, 0, 1.0f);
float cell_width2 = (1.0f / static_cast(grid_width_));
float cell_height2 = (1.0f / static_cast(grid_height_));
- c2.PushTransform();
- c2.Translate((x_min_ + x_max_) * 0.5f, 0, (z_min_ + z_max_) * 0.5f);
- c2.Scale(x_max_ - x_min_, 1, z_max_ - z_min_);
- c2.PushTransform();
- c2.Scale(1, 0.01f, 1);
- c2.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kBox));
- c2.PopTransform();
- c2.Translate(-0.5f + 0.5f * cell_width2, 0, -0.5f + 0.5f * cell_height2);
- for (int x = 0; x < grid_width_; x++) {
- for (int z = 0; z < grid_height_; z++) {
- int cell_index = z * grid_width_ + x;
- assert(cell_index >= 0 && cell_index < static_cast(glow_.size()));
- if (glow_[cell_index]) {
- c2.SetColor(1, 1, 1, 0.2f);
- } else {
- c2.SetColor(1, 0, 0, 0.2f);
- }
- c2.PushTransform();
- c2.Translate(static_cast(x) / static_cast(grid_width_),
- cells_[cell_index].height_confirmed_empty_,
- static_cast(z) / static_cast(grid_height_));
- c2.Scale(0.95f * cell_width2, 0.01f, 0.95f * cell_height2);
- c2.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kBox));
- c2.PopTransform();
- if (glow_[cell_index]) {
- c2.SetColor(1, 1, 1, 0.2f);
- } else {
- c2.SetColor(0, 0, 1, 0.2f);
- }
- c2.PushTransform();
- c2.Translate(static_cast(x) / static_cast(grid_width_),
- cells_[cell_index].height_confirmed_collide_,
- static_cast(z) / static_cast(grid_height_));
- c2.Scale(0.95f * cell_width2, 0.01f, 0.95f * cell_height2);
- c2.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kBox));
- c2.PopTransform();
+ {
+ auto xf = c2.ScopedTransform();
- glow_[cell_index] = 0;
+ c2.Translate((x_min_ + x_max_) * 0.5f, 0, (z_min_ + z_max_) * 0.5f);
+ c2.Scale(x_max_ - x_min_, 1, z_max_ - z_min_);
+ {
+ auto xf = c2.ScopedTransform();
+ c2.Scale(1, 0.01f, 1);
+ c2.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kBox));
+ }
+ c2.Translate(-0.5f + 0.5f * cell_width2, 0, -0.5f + 0.5f * cell_height2);
+ for (int x = 0; x < grid_width_; x++) {
+ for (int z = 0; z < grid_height_; z++) {
+ int cell_index = z * grid_width_ + x;
+ assert(cell_index >= 0
+ && cell_index < static_cast(glow_.size()));
+ if (glow_[cell_index]) {
+ c2.SetColor(1, 1, 1, 0.2f);
+ } else {
+ c2.SetColor(1, 0, 0, 0.2f);
+ }
+ {
+ auto xf = c2.ScopedTransform();
+ c2.Translate(
+ static_cast(x) / static_cast(grid_width_),
+ cells_[cell_index].height_confirmed_empty_,
+ static_cast(z) / static_cast(grid_height_));
+ c2.Scale(0.95f * cell_width2, 0.01f, 0.95f * cell_height2);
+ c2.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kBox));
+ }
+ if (glow_[cell_index]) {
+ c2.SetColor(1, 1, 1, 0.2f);
+ } else {
+ c2.SetColor(0, 0, 1, 0.2f);
+ }
+ {
+ auto xf = c2.ScopedTransform();
+ c2.Translate(
+ static_cast(x) / static_cast(grid_width_),
+ cells_[cell_index].height_confirmed_collide_,
+ static_cast(z) / static_cast(grid_height_));
+ c2.Scale(0.95f * cell_width2, 0.01f, 0.95f * cell_height2);
+ c2.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kBox));
+ }
+ glow_[cell_index] = 0;
+ }
}
}
- c2.PopTransform();
c2.Submit();
}
}
diff --git a/src/ballistica/base/graphics/component/render_component.cc b/src/ballistica/base/graphics/component/render_component.cc
index c1e938a9..c10083a7 100644
--- a/src/ballistica/base/graphics/component/render_component.cc
+++ b/src/ballistica/base/graphics/component/render_component.cc
@@ -4,10 +4,10 @@
namespace ballistica::base {
-void RenderComponent::ScissorPush(const Rect& rIn) {
+void RenderComponent::ScissorPush(const Rect& rect) {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kScissorPush);
- cmd_buffer_->PutFloats(rIn.l, rIn.b, rIn.r, rIn.t);
+ cmd_buffer_->PutFloats(rect.l, rect.b, rect.r, rect.t);
}
#if BA_DEBUG_BUILD
diff --git a/src/ballistica/base/graphics/component/render_component.h b/src/ballistica/base/graphics/component/render_component.h
index bd59acce..c7fdaaa0 100644
--- a/src/ballistica/base/graphics/component/render_component.h
+++ b/src/ballistica/base/graphics/component/render_component.h
@@ -9,33 +9,86 @@
namespace ballistica::base {
+/// RenderComponents are used to assemble command streams to send to the
+/// renderer. These do a lot of extra work in debug builds to make sure
+/// valid commands are being constructed, so it is best to iterate on them
+/// in debug mode when possible.
+///
+/// The general workflow with a RenderComponents is to set all 'config'
+/// options at the beginning and then to issue one or more draw commands
+/// after. Check the source of each call for EnsureConfiguring() or
+/// EnsureDrawing() to see which is which. Flipping from configuring to
+/// drawing can cause shader binding or other work to be done in the
+/// graphics api, so switches back and forth should be minimized.
+///
+/// RenderComponent output goes to a specific draw list in the renderer.
+/// Depending on the type of RenderPass, there may be a single draw-list,
+/// transparent and opaque draw-lists, draw-lists for different shaders,
+/// etc. RenderComponents currently must be sure to only draw to a single
+/// draw list; otherwise things like PushTransform/PopTransforms may affect
+/// different draw lists. Stay tuned for this system to evolve into
+/// something more foolproof.
class RenderComponent {
- public:
- class ScopedTransformObj {
+ private:
+ class ScopedTransformObj_ {
public:
- explicit ScopedTransformObj(RenderComponent* c) : c_{c} {
+ explicit ScopedTransformObj_(RenderComponent* c) : c_{c} {
c_->PushTransform();
}
- ~ScopedTransformObj() { c_->PopTransform(); }
+ ~ScopedTransformObj_() { c_->PopTransform(); }
private:
RenderComponent* c_;
};
- explicit RenderComponent(RenderPass* pass)
- : state_(State::kConfiguring), pass_(pass), cmd_buffer_(nullptr) {}
+ class ScopedScissorObj_ {
+ public:
+ explicit ScopedScissorObj_(RenderComponent* c, const Rect& r) : c_{c} {
+ c_->ScissorPush(r);
+ }
+ ~ScopedScissorObj_() { c_->ScissorPop(); }
+
+ private:
+ RenderComponent* c_;
+ };
+
+ public:
+ explicit RenderComponent(RenderPass* pass) : pass_(pass) {
+ assert(g_base->InLogicThread());
+ }
+
~RenderComponent() {
+ assert(g_base->InLogicThread());
+
if (state_ != State::kSubmitted) {
- Log(LogLevel::kError,
- "RenderComponent dying without submit() having been called.");
+ Submit();
}
}
+
+ /// End current drawing by this component. This is implicitly done when a
+ /// component goes out of scope, but one may choose to do this explicitly
+ /// to allow other components to draw while this one still exists (only
+ /// one RenderComponent can be actively drawing in a frame-def at a time).
+ void Submit() {
+ if (state_ != State::kSubmitted) {
+#if BA_DEBUG_BUILD
+ if (state_ == State::kDrawing) {
+ // If we were drawing, let the frame-def know we're done.
+ assert(pass_->frame_def()->active_render_component() == this);
+ pass_->frame_def()->set_active_render_component(nullptr);
+ }
+#endif
+ state_ = State::kSubmitted;
+ }
+ }
+
void DrawMeshAsset(MeshAsset* mesh, uint32_t flags = 0) {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kDrawMeshAsset);
cmd_buffer_->PutInt(flags);
cmd_buffer_->PutMeshAsset(mesh);
}
+
void DrawMeshAssetInstanced(MeshAsset* mesh,
const std::vector& matrices,
int flags = 0) {
@@ -47,6 +100,7 @@ class RenderComponent {
cmd_buffer_->PutMeshAsset(mesh);
cmd_buffer_->PutMatrices(matrices);
}
+
void DrawMesh(Mesh* m, int flags = 0) {
EnsureDrawing();
if (m->IsValid()) {
@@ -56,134 +110,147 @@ class RenderComponent {
cmd_buffer_->PutMeshData(m->mesh_data_client_handle()->mesh_data);
}
}
+
void DrawScreenQuad() {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kDrawScreenQuad);
}
- // draw triangles using old-school gl format.. only for debugging
- // and not supported in all configurations
+
+ // Draw triangles using old-school gl format.. only for debugging and not
+ // supported in all configurations.
void BeginDebugDrawTriangles() {
EnsureDrawing();
cmd_buffer_->PutCommand(
RenderCommandBuffer::Command::kBeginDebugDrawTriangles);
}
+
void BeginDebugDrawLines() {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kBeginDebugDrawLines);
}
+
void Vertex(float x, float y, float z) {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kDebugDrawVertex3);
cmd_buffer_->PutFloats(x, y, z);
}
+
void End() {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kEndDebugDraw);
}
- void ScissorPush(const Rect& rIn);
- void ScissorPop() {
- EnsureDrawing();
- cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kScissorPop);
- }
+
void PushTransform() {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kPushTransform);
}
+
void PopTransform() {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kPopTransform);
}
- auto ScopedTransform() -> ScopedTransformObj {
- return ScopedTransformObj(this);
+
+ /// Add a transform push/pop to the component. Remember to assign the
+ /// result to a variable or the pop will be immediate.
+ auto ScopedTransform() -> ScopedTransformObj_ {
+ return ScopedTransformObj_(this);
}
+
+ /// Add a scissor push/pop to the component. Remember to assign the result
+ /// to a variable or the pop will be immediate.
+ auto ScopedScissor(const Rect& rect) -> ScopedScissorObj_ {
+ return ScopedScissorObj_(this, rect);
+ }
+
void Translate(float x, float y) {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kTranslate2);
cmd_buffer_->PutFloats(x, y);
}
+
void Translate(float x, float y, float z) {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kTranslate3);
cmd_buffer_->PutFloats(x, y, z);
}
+
void CursorTranslate() {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kCursorTranslate);
}
+
void Rotate(float angle, float x, float y, float z) {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kRotate);
cmd_buffer_->PutFloats(angle, x, y, z);
}
+
void Scale(float x, float y) {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kScale2);
cmd_buffer_->PutFloats(x, y);
}
+
void Scale(float x, float y, float z) {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kScale3);
cmd_buffer_->PutFloats(x, y, z);
}
+
void ScaleUniform(float s) {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kScaleUniform);
cmd_buffer_->PutFloat(s);
}
+
void MultMatrix(const float* t) {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kMultMatrix);
cmd_buffer_->PutFloatArray16(t);
}
+
#if BA_VR_BUILD
void VRTransformToRightHand() {
EnsureDrawing();
cmd_buffer_->PutCommand(
RenderCommandBuffer::Command::kTransformToRightHand);
}
+
void VRTransformToLeftHand() {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kTransformToLeftHand);
}
+
void VRTransformToHead() {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kTransformToHead);
}
#endif // BA_VR_BUILD
+
void TranslateToProjectedPoint(float x, float y, float z) {
EnsureDrawing();
cmd_buffer_->PutCommand(
RenderCommandBuffer::Command::kTranslateToProjectedPoint);
cmd_buffer_->PutFloats(x, y, z);
}
+
void FlipCullFace() {
EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kFlipCullFace);
}
- void Submit() {
- if (state_ != State::kSubmitted) {
- // If we were drawing, make note that we're done.
- if (state_ == State::kDrawing) {
-#if BA_DEBUG_BUILD
- assert(pass_->frame_def()->defining_component());
- pass_->frame_def()->set_defining_component(false);
-#endif
- }
- state_ = State::kSubmitted;
- }
- }
protected:
enum class State { kConfiguring, kDrawing, kSubmitted };
void EnsureConfiguring() {
if (state_ != State::kConfiguring) {
- // if we were drawing, make note that we're done
#if BA_DEBUG_BUILD
+ // FIXME: currently releasing status as active-render-component here
+ // but should perhaps hold on to it for consistency.
if (state_ == State::kDrawing) {
- assert(pass_->frame_def()->defining_component());
- pass_->frame_def()->set_defining_component(false);
+ assert(pass_->frame_def()->active_render_component() == this);
+ pass_->frame_def()->set_active_render_component(nullptr);
}
-#endif // BA_DEBUG_BUILD
+#endif
state_ = State::kConfiguring;
}
}
@@ -209,33 +276,31 @@ class RenderComponent {
// Given a shader type, sets up the config target buffer.
void ConfigForShading(ShadingType shading_type) {
// Determine which buffer to write to, etc.
- // Debugging: if we've got transparent-only or opaque-only mode flipped on,
- // make sure only those type of components are being submitted.
+
#if BA_DEBUG_BUILD
+ // Debugging: if we've got transparent-only or opaque-only mode flipped
+ // on, make sure only those type of components are being submitted.
ConfigForShadingDebugChecks(shading_type);
// Also make sure only transparent stuff is going into the
- // light/shadow/overlay3D passes (we skip rendering the opaque lists there
- // since there shouldn't be anything in them, and we're not using depth
- // for those so it wouldn't be much of an optimization..)
+ // light/shadow/overlay3D passes (we skip rendering the opaque lists
+ // there since there shouldn't be anything in them, and we're not using
+ // depth for those so it wouldn't be much of an optimization).
if ((pass_->type() == RenderPass::Type::kLightPass
|| pass_->type() == RenderPass::Type::kLightShadowPass
|| pass_->type() == RenderPass::Type::kOverlay3DPass)
&& !Graphics::IsShaderTransparent(shading_type)) {
- throw Exception(
- "Opaque component submitted to light/shadow/overlay3d pass;"
- " not cool man.");
+ FatalError("Opaque component submitted to light/shadow/overlay3d pass.");
}
// Likewise the blit pass should consist solely of opaque stuff.
if (pass_->type() == RenderPass::Type::kBlitPass
&& Graphics::IsShaderTransparent(shading_type)) {
- throw Exception(
- "Transparent component submitted to blit pass;"
- " not cool man.");
+ FatalError("Transparent component submitted to blit pass.");
}
#endif // BA_DEBUG_BUILD
- // Certain passes (overlay, etc) draw objects in the order
- // provided. Other passes group by shader for efficiency.
+
+ // Certain passes (overlay, etc) draw objects in the order provided.
+ // Other passes group by shader for efficiency.
if (pass_->UsesWorldLists()) {
cmd_buffer_ = pass_->GetCommands(shading_type);
} else {
@@ -255,21 +320,28 @@ class RenderComponent {
if (state_ != State::kDrawing) {
WriteConfig();
state_ = State::kDrawing;
- // make sure we're the only one drawing until we're submitted
#if BA_DEBUG_BUILD
- assert(!pass_->frame_def()->defining_component());
- pass_->frame_def()->set_defining_component(true);
-#endif // BA_DEBUG_BUILD
+ // Let the frame-def know we're the active component drawing to it now.
+ assert(pass_->frame_def()->active_render_component() == nullptr);
+ pass_->frame_def()->set_active_render_component(this);
+#endif
}
}
- // subclasses should override this to dump
- // their needed data to the stream
+ // Subclasses should override this to dump their needed data to the
+ // stream.
virtual void WriteConfig() = 0;
- protected:
- RenderCommandBuffer* cmd_buffer_;
- State state_;
+ RenderCommandBuffer* cmd_buffer_{};
+ State state_{State::kConfiguring};
RenderPass* pass_;
+
+ public:
+ void ScissorPush(const Rect& rect);
+
+ void ScissorPop() {
+ EnsureDrawing();
+ cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kScissorPop);
+ }
};
} // namespace ballistica::base
diff --git a/src/ballistica/base/graphics/component/simple_component.cc b/src/ballistica/base/graphics/component/simple_component.cc
index 797b4e22..6075dc19 100644
--- a/src/ballistica/base/graphics/component/simple_component.cc
+++ b/src/ballistica/base/graphics/component/simple_component.cc
@@ -5,9 +5,9 @@
namespace ballistica::base {
void SimpleComponent::WriteConfig() {
- // if we're transparent we don't want to do optimization-based
- // shader swapping (ie: when color is 1). This is because it can
- // affect draw order, which is important unlike with opaque stuff.
+ // If we're transparent, we don't want to do optimization-based shader
+ // swapping (ie: when color is 1). This is because it can affect draw
+ // order, which is important unlike with opaque stuff.
if (transparent_) {
if (texture_.Exists()) {
if (colorize_texture_.Exists()) {
@@ -52,7 +52,7 @@ void SimpleComponent::WriteConfig() {
cmd_buffer_->PutTexture(colorize_texture_);
}
} else {
- // non-colorized with texture
+ // Non-colorized with texture.
if (double_sided_) {
assert(!mask_texture_.Exists()); // unimplemented combo
assert(flatness_ == 0.0f); // unimplemented combo
@@ -111,7 +111,7 @@ void SimpleComponent::WriteConfig() {
}
} else {
if (flatness_ != 0.0f) {
- assert(!mask_texture_.Exists()); // unimplemented
+ assert(!mask_texture_.Exists()); // unimplemented combo
ConfigForShading(
ShadingType::kSimpleTextureModulatedTransFlatness);
cmd_buffer_->PutInt(premultiplied_);
@@ -120,8 +120,8 @@ void SimpleComponent::WriteConfig() {
cmd_buffer_->PutTexture(texture_);
} else {
if (mask_texture_.Exists()) {
- // currently mask functionality requires colorize too, so
- // just send a black texture for that..
+ // Currently mask functionality requires colorize too, so
+ // just send a black texture for that.
ConfigForShading(
ShadingType::
kSimpleTextureModulatedTransparentColorized2Masked);
@@ -165,12 +165,12 @@ void SimpleComponent::WriteConfig() {
}
}
} else {
- // when we're opaque we can do some shader-swapping optimizations
+ // When we're opaque, we can do some shader-swapping optimizations
// since draw order doesn't matter.
assert(flatness_ == 0.0f); // unimplemented combo
assert(glow_amount_ == 0.0f); // unimplemented combo
assert(shadow_opacity_ == 0.0f); // unimplemented combo
- assert(!double_sided_); // not implemented
+ assert(!double_sided_); // unimplemented combo
assert(!mask_uv2_texture_.Exists()); // unimplemented combo
if (texture_.Exists()) {
if (colorize_texture_.Exists()) {
@@ -194,8 +194,8 @@ void SimpleComponent::WriteConfig() {
} else {
assert(!do_colorize_2_); // unsupported combo
if (mask_texture_.Exists()) {
- // currently mask functionality requires colorize too, so
- // we have to send a black texture along for that..
+ // Currently mask functionality requires colorize too, so
+ // we have to send a black texture along for that.
ConfigForShading(
ShadingType::kSimpleTextureModulatedColorized2Masked);
cmd_buffer_->PutFloats(color_r_, color_g_, color_b_, color_a_,
@@ -207,7 +207,7 @@ void SimpleComponent::WriteConfig() {
g_base->assets->SysTexture(SysTextureID::kBlack));
cmd_buffer_->PutTexture(mask_texture_);
} else {
- // if no color was provided we can do a super-cheap version
+ // If no color was provided, we can do a super-cheap version.
if (!have_color_) {
ConfigForShading(ShadingType::kSimpleTexture);
cmd_buffer_->PutTexture(texture_);
diff --git a/src/ballistica/base/graphics/component/simple_component.h b/src/ballistica/base/graphics/component/simple_component.h
index c40bb75f..6452401a 100644
--- a/src/ballistica/base/graphics/component/simple_component.h
+++ b/src/ballistica/base/graphics/component/simple_component.h
@@ -36,18 +36,22 @@ class SimpleComponent : public RenderComponent {
have_color_(false),
double_sided_(false),
do_colorize_2_(false) {}
+
void SetPremultiplied(bool val) {
EnsureConfiguring();
premultiplied_ = val;
}
+
void SetTransparent(bool val) {
EnsureConfiguring();
transparent_ = val;
}
+
void SetTexture(TextureAsset* t) {
EnsureConfiguring();
texture_ = t;
}
+
void SetTexture(const Object::Ref& t) {
EnsureConfiguring();
texture_ = t;
@@ -59,31 +63,36 @@ class SimpleComponent : public RenderComponent {
EnsureConfiguring();
colorize_texture_ = t;
}
- // red multiplies source color, green adds colorize1-color,
- // and blue adds white
- // (currently requires colorize1 and colorize 2 to be set)
+
+ // Red multiplies source color, green adds colorize1-color, and blue adds
+ // white (currently requires colorize1 and colorize 2 to be set).
void SetMaskTexture(TextureAsset* t) {
EnsureConfiguring();
mask_texture_ = t;
}
+
void SetMaskUV2Texture(TextureAsset* t) {
EnsureConfiguring();
mask_uv2_texture_ = t;
}
+
void ClearMaskUV2Texture() {
EnsureConfiguring();
mask_uv2_texture_.Clear();
}
+
void SetDoubleSided(bool enable) {
EnsureConfiguring();
double_sided_ = enable;
}
+
void SetColor(float r, float g, float b, float a = 1.0f) {
- // we support fast inline color changes with drawing streams
- // (avoids having to re-send a whole configure for every color change)
- // ..make sure to only allow this if we have a color already; otherwise we
+ // We support fast inline color changes with drawing streams (avoids
+ // having to re-send a whole configure for every color change).
+
+ // Make sure to only allow this if we have a color already; otherwise we
// need to config since we might be implicitly switch shaders by setting
- // color
+ // color.
if (state_ == State::kDrawing && have_color_) {
cmd_buffer_->PutCommand(
RenderCommandBuffer::Command::kSimpleComponentInlineColor);
@@ -97,6 +106,7 @@ class SimpleComponent : public RenderComponent {
color_b_ = b;
color_a_ = a;
}
+
void SetColorizeColor(float r, float g, float b, float a = 1.0f) {
EnsureConfiguring();
colorize_color_r_ = r;
@@ -104,6 +114,7 @@ class SimpleComponent : public RenderComponent {
colorize_color_b_ = b;
colorize_color_a_ = a;
}
+
void SetColorizeColor2(float r, float g, float b, float a = 1.0f) {
EnsureConfiguring();
colorize_color2_r_ = r;
@@ -112,6 +123,7 @@ class SimpleComponent : public RenderComponent {
colorize_color2_a_ = a;
do_colorize_2_ = true;
}
+
void SetShadow(float offsetX, float offsetY, float blur, float opacity) {
EnsureConfiguring();
shadow_offset_x_ = offsetX;
@@ -119,11 +131,13 @@ class SimpleComponent : public RenderComponent {
shadow_blur_ = blur;
shadow_opacity_ = opacity;
}
- void setGlow(float amount, float blur) {
+
+ void SetGlow(float amount, float blur) {
EnsureConfiguring();
glow_amount_ = amount;
glow_blur_ = blur;
}
+
void SetFlatness(float flatness) {
EnsureConfiguring();
flatness_ = flatness;
diff --git a/src/ballistica/base/graphics/gl/framebuffer_object_gl.h b/src/ballistica/base/graphics/gl/framebuffer_object_gl.h
new file mode 100644
index 00000000..fc767c51
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/framebuffer_object_gl.h
@@ -0,0 +1,334 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_FRAMEBUFFER_OBJECT_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_FRAMEBUFFER_OBJECT_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/renderer_gl.h"
+#include "ballistica/base/graphics/graphics_server.h"
+
+namespace ballistica::base {
+
+class RendererGL::FramebufferObjectGL : public Framebuffer {
+ public:
+ FramebufferObjectGL(RendererGL* renderer_in, int width_in, int height_in,
+ bool linear_interp_in, bool depth_in, bool is_texture_in,
+ bool depth_is_texture_in, bool high_quality_in,
+ bool msaa_in, bool alpha_in)
+ : width_(width_in),
+ height_(height_in),
+ linear_interp_(linear_interp_in),
+ depth_(depth_in),
+ is_texture_(is_texture_in),
+ depth_is_texture_(depth_is_texture_in),
+ renderer_(renderer_in),
+ high_quality_(high_quality_in),
+ msaa_(msaa_in),
+ alpha_(alpha_in) {
+ // Desktop stuff is always high-quality.
+#if BA_OSTYPE_MACOS || BA_OSTYPE_LINUX || BA_OSTYPE_WINDOWS
+ high_quality_ = true;
+#endif
+
+ // Things are finally getting to the point where we can default to
+ // desktop quality on some mobile stuff.
+#if BA_OSTYPE_ANDROID
+ if (renderer_->is_tegra_k1_) {
+ high_quality_ = true;
+ }
+#endif
+
+ Load();
+ }
+
+ ~FramebufferObjectGL() override { Unload(); }
+
+ void Load(bool force_low_quality = false) {
+ if (loaded_) return;
+ assert(g_base->InGraphicsThread());
+ BA_DEBUG_CHECK_GL_ERROR;
+ GLenum status;
+ BA_DEBUG_CHECK_GL_ERROR;
+ glGenFramebuffers(1, &framebuffer_);
+ renderer_->BindFramebuffer(framebuffer_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ bool do_high_quality = high_quality_;
+ if (force_low_quality) do_high_quality = false;
+ int samples = 0;
+ if (msaa_) {
+ // Can't multisample with texture buffers currently.
+ assert(!is_texture_ && !depth_is_texture_);
+
+ int target_samples =
+ renderer_->GetMSAASamplesForFramebuffer_(width_, height_);
+
+ if (do_high_quality) {
+ samples = std::min(target_samples, renderer_->msaa_max_samples_rgb8());
+ } else {
+ samples =
+ std::min(target_samples, renderer_->msaa_max_samples_rgb565());
+ }
+ }
+ if (is_texture_) {
+ // Attach a texture for the color target.
+ glGenTextures(1, &texture_);
+ renderer_->BindTexture_(GL_TEXTURE_2D, texture_);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
+ linear_interp_ ? GL_LINEAR : GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
+ linear_interp_ ? GL_LINEAR : GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ // On android/ios lets go with 16 bit unless they explicitly request
+ // high quality.
+#if BA_OSTYPE_ANDROID || BA_OSTYPE_IOS_TVOS
+ GLenum format;
+ if (alpha_) {
+ format = do_high_quality ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT_4_4_4_4;
+ } else {
+ format = do_high_quality ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT_5_6_5;
+ }
+#else
+ GLenum format = GL_UNSIGNED_BYTE;
+#endif
+ // if (srgbTest) {
+ // glTexImage2D(GL_TEXTURE_2D, 0, alpha_?GL_SRGB8_ALPHA8:GL_SRGB8,
+ // _width, _height, 0, alpha_?GL_RGBA:GL_RGB, format, nullptr);
+ // } else {
+ glTexImage2D(GL_TEXTURE_2D, 0, alpha_ ? GL_RGBA : GL_RGB, width_, height_,
+ 0, alpha_ ? GL_RGBA : GL_RGB, format, nullptr);
+ // }
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_TEXTURE_2D, texture_, 0);
+ } else {
+ // Regular renderbuffer.
+ assert(!alpha_); // fixme
+#if BA_OSTYPE_IOS_TVOS
+ GLenum format =
+ GL_RGB565; // FIXME; need to pull ES3 headers in for GL_RGB8
+#elif BA_OSTYPE_ANDROID
+ GLenum format = do_high_quality ? GL_RGB8 : GL_RGB565;
+#else
+ GLenum format = GL_RGB8;
+#endif
+ glGenRenderbuffers(1, &render_buffer_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ glBindRenderbuffer(GL_RENDERBUFFER, render_buffer_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ if (samples > 0) {
+#if BA_OSTYPE_IOS_TVOS
+ throw Exception();
+#else
+ glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, format,
+ width_, height_);
+#endif
+ } else {
+ glRenderbufferStorage(GL_RENDERBUFFER, format, width_, height_);
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_RENDERBUFFER, render_buffer_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+ if (depth_) {
+ if (depth_is_texture_) {
+ glGenTextures(1, &depth_texture_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ renderer_->BindTexture_(GL_TEXTURE_2D, depth_texture_);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+ BA_DEBUG_CHECK_GL_ERROR;
+ // FIXME: need to pull in ES3 stuff for iOS to get GL_DEPTH_COMPONENT24.
+ // #if BA_OSTYPE_IOS_TVOS
+ // glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width_,
+ // height_, 0,
+ // GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, nullptr);
+ // #else
+ if (do_high_quality) {
+ // #if BA_OSTYPE_ANDROID
+ // assert(g_running_es3);
+ // #endif // BA_OSTYPE_ANDROID
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, width_, height_,
+ 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
+ } else {
+ glTexImage2D(
+ GL_TEXTURE_2D, 0,
+ renderer_->gl_is_es() ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT,
+ width_, height_, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT,
+ nullptr);
+ }
+ // #endif // BA_OSTYPE_IOS_TVOS
+
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_TEXTURE_2D, depth_texture_, 0);
+
+ BA_DEBUG_CHECK_GL_ERROR;
+ } else {
+ // Just use a plain old renderbuffer if we don't need it as a texture
+ // (this is more widely supported).
+ glGenRenderbuffers(1, &depth_render_buffer_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ glBindRenderbuffer(GL_RENDERBUFFER, depth_render_buffer_);
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ if (samples > 0) {
+ // #if BA_OSTYPE_IOS_TVOS
+ // throw Exception();
+ // #else
+ // (GL_DEPTH_COMPONENT24 not available in ES2 it looks like)
+ bool do24;
+ // #if BA_OSTYPE_ANDROID
+ // do24 = (do_high_quality && g_running_es3);
+ // #else
+ do24 = do_high_quality;
+ // #endif
+
+ glRenderbufferStorageMultisample(
+ GL_RENDERBUFFER, samples,
+ do24 ? GL_DEPTH_COMPONENT24 : GL_DEPTH_COMPONENT16, width_,
+ height_);
+ // (do_high_quality &&
+ // g_running_es3)?GL_DEPTH_COMPONENT24:GL_DEPTH_COMPONENT16, _width,
+ // _height);
+ // #endif
+ } else {
+ // FIXME - need to pull in es3 headers to get GL_DEPTH_COMPONENT24 on
+ // iOS
+ // #if BA_OSTYPE_IOS_TVOS
+ // GLenum format = GL_DEPTH_COMPONENT16;
+ // #else
+ // GL_DEPTH_COMPONENT24 not available in ES2 it looks like.
+ GLenum format = (do_high_quality && renderer_->gl_is_es())
+ ? GL_DEPTH_COMPONENT24
+ : GL_DEPTH_COMPONENT16;
+ // #endif
+
+ glRenderbufferStorage(GL_RENDERBUFFER, format, width_, height_);
+ }
+
+ BA_DEBUG_CHECK_GL_ERROR;
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, depth_render_buffer_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+ }
+
+ status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
+
+ if (status != GL_FRAMEBUFFER_COMPLETE) {
+ const char* version = (const char*)glGetString(GL_VERSION);
+ const char* vendor = (const char*)glGetString(GL_VENDOR);
+ const char* renderer = (const char*)glGetString(GL_RENDERER);
+ throw Exception(
+ "Framebuffer setup failed for " + std::to_string(width_) + " by "
+ + std::to_string(height_) + " fb with depth " + std::to_string(depth_)
+ + " asTex " + std::to_string(depth_is_texture_) + " gl-version "
+ + version + " vendor " + vendor + " renderer " + renderer);
+ }
+ // GLint enc;
+ // glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER,
+ // GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, &enc); if
+ // (enc == GL_SRGB) {
+ // Log(LogLevel::kInfo, "GOT SRGB!!!!!!!!!!!");
+ // } else if (enc == GL_LINEAR) {
+ // Log(LogLevel::kInfo, "GOT LINEAR...");
+ // } else {
+ // Log(LogLevel::kInfo, "GOT OTHER..");
+ // }
+ loaded_ = true;
+ }
+
+ void Unload() {
+ assert(g_base->InGraphicsThread());
+ if (!loaded_) return;
+
+ // If our textures are currently bound as anything, clear that out.
+ // (otherwise a new texture with that same ID won't be bindable)
+ for (int& i : renderer_->bound_textures_2d_) {
+ if (i == texture_) { // NOLINT(bugprone-branch-clone)
+ i = -1;
+ } else if (depth_ && (i == depth_texture_)) {
+ i = -1;
+ }
+ }
+
+ if (!g_base->graphics_server->renderer_context_lost()) {
+ // Tear down the FBO and texture attachment
+ if (is_texture_) {
+ glDeleteTextures(1, &texture_);
+ } else {
+ glDeleteRenderbuffers(1, &render_buffer_);
+ }
+ if (depth_) {
+ if (depth_is_texture_) {
+ glDeleteTextures(1, &depth_texture_);
+ } else {
+ glDeleteRenderbuffers(1, &depth_render_buffer_);
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+
+ // If this one is current, make sure we re-bind next time.
+ // (otherwise we might prevent a new framebuffer with a recycled id from
+ // binding)
+ if (renderer_->active_framebuffer_ == framebuffer_) {
+ renderer_->active_framebuffer_ = -1;
+ }
+ glDeleteFramebuffers(1, &framebuffer_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+ loaded_ = false;
+ }
+
+ void Bind() {
+ assert(g_base->InGraphicsThread());
+ renderer_->BindFramebuffer(framebuffer_);
+ // if (time(nullptr)%2 == 0) {
+ // glDisable(GL_FRAMEBUFFER_SRGB);
+ // }
+ }
+
+ auto texture() const -> GLuint {
+ assert(is_texture_);
+ return texture_;
+ }
+
+ auto depth_texture() const -> GLuint {
+ assert(depth_ && depth_is_texture_);
+ return depth_texture_;
+ }
+
+ auto width() const -> int { return width_; }
+ auto height() const -> int { return height_; }
+ auto id() const -> GLuint { return framebuffer_; }
+
+ private:
+ RendererGL* renderer_{};
+ bool depth_{};
+ bool is_texture_{};
+ bool depth_is_texture_{};
+ bool high_quality_{};
+ bool msaa_{};
+ bool alpha_{};
+ bool linear_interp_{};
+ bool loaded_{};
+ int width_{}, height_{};
+ GLuint framebuffer_{};
+ GLuint texture_{};
+ GLuint depth_texture_{};
+ GLuint render_buffer_{};
+ GLuint depth_render_buffer_{};
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_FRAMEBUFFER_OBJECT_GL_H_
diff --git a/src/ballistica/base/graphics/gl/gl_sys.cc b/src/ballistica/base/graphics/gl/gl_sys.cc
index 9073e671..5d6e42ab 100644
--- a/src/ballistica/base/graphics/gl/gl_sys.cc
+++ b/src/ballistica/base/graphics/gl/gl_sys.cc
@@ -3,364 +3,58 @@
#if BA_ENABLE_OPENGL
#include "ballistica/base/graphics/gl/gl_sys.h"
-#include "ballistica/base/app_adapter/app_adapter_sdl.h"
-#include "ballistica/base/base.h"
-#include "ballistica/core/core.h"
+#include "ballistica/shared/ballistica.h"
-#if BA_OSTYPE_ANDROID
-#include
-#if !BA_USE_ES3_INCLUDES
-#include "ballistica/core/platform/android/android_gl3.h"
-#endif
-#endif
+// #include "ballistica/base/app_adapter/app_adapter_sdl.h"
+// #include "ballistica/base/base.h"
+// #include "ballistica/core/core.h"
-#if BA_OSTYPE_WINDOWS
-#pragma comment(lib, "opengl32.lib")
-#pragma comment(lib, "glu32.lib")
-#endif
+// #if BA_OSTYPE_ANDROID
+// #include
+// #if !BA_USE_ES3_INCLUDES
+// #include "ballistica/core/platform/android/android_gl3.h"
+// #endif
+// #endif
-#if BA_OSTYPE_MACOS
-#include
-#include
-#include
-#endif
+// #if BA_OSTYPE_MACOS
+// #include
+// #include
+// #include
+// #endif
-#if BA_DEBUG_BUILD
-#define DEBUG_CHECK_GL_ERROR \
- { \
- GLenum err = glGetError(); \
- if (err != GL_NO_ERROR) \
- Log(LogLevel::kError, "OPENGL ERROR AT LINE " + std::to_string(__LINE__) \
- + ": " + GLErrorToString(err)); \
- }
-#else
-#define DEBUG_CHECK_GL_ERROR
-#endif
+// #if BA_OSTYPE_IOS
+// void (*glInvalidateFramebuffer)(GLenum target, GLsizei num_attachments,
+// const GLenum* attachments) = nullptr;
+// #endif
-#if BA_OSTYPE_ANDROID
-PFNGLDISCARDFRAMEBUFFEREXTPROC _glDiscardFramebufferEXT = nullptr;
-#endif
-
-#if BA_OSTYPE_WINDOWS
-PFNGLGETINTERNALFORMATIVPROC glGetInternalformativ = nullptr;
-PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC
-glGetFramebufferAttachmentParameteriv = nullptr;
-PFNGLBLENDFUNCSEPARATEPROC glBlendFuncSeparate = nullptr;
-PFNGLACTIVETEXTUREPROC glActiveTexture = nullptr;
-PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB = nullptr;
-PFNGLPOINTPARAMETERFVARBPROC glPointParameterfvARB = nullptr;
-PFNGLPOINTPARAMETERFARBPROC glPointParameterfARB = nullptr;
-PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT = nullptr;
-PFNGLCREATEPROGRAMPROC glCreateProgram = nullptr;
-PFNGLCREATESHADERPROC glCreateShader = nullptr;
-PFNGLSHADERSOURCEPROC glShaderSource = nullptr;
-PFNGLCOMPILESHADERPROC glCompileShader = nullptr;
-PFNGLLINKPROGRAMPROC glLinkProgram = nullptr;
-PFNGLGETINFOLOGARBPROC glGetInfoLogARB = nullptr;
-PFNGLATTACHSHADERPROC glAttachShader = nullptr;
-PFNGLUSEPROGRAMOBJECTARBPROC glUseProgram = nullptr;
-PFNGLGENERATEMIPMAPPROC glGenerateMipmap = nullptr;
-PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer = nullptr;
-PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer = nullptr;
-PFNGLBINDVERTEXARRAYPROC glBindVertexArray = nullptr;
-PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation = nullptr;
-PFNGLUNIFORM1IPROC glUniform1i = nullptr;
-PFNGLUNIFORM1FPROC glUniform1f = nullptr;
-PFNGLUNIFORM1FVPROC glUniform1fv = nullptr;
-PFNGLUNIFORM2FPROC glUniform2f = nullptr;
-PFNGLUNIFORM3FPROC glUniform3f = nullptr;
-PFNGLUNIFORM4FPROC glUniform4f = nullptr;
-PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers = nullptr;
-PFNGLGENBUFFERSPROC glGenBuffers = nullptr;
-PFNGLGENVERTEXARRAYSPROC glGenVertexArrays = nullptr;
-PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D = nullptr;
-PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers = nullptr;
-PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer = nullptr;
-PFNGLBINDBUFFERPROC glBindBuffer = nullptr;
-PFNGLBUFFERDATAPROC glBufferData = nullptr;
-PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage = nullptr;
-PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glRenderbufferStorageMultisample =
- nullptr;
-PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer = nullptr;
-PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus = nullptr;
-PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers = nullptr;
-PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers = nullptr;
-PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer = nullptr;
-PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray = nullptr;
-PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray = nullptr;
-PFNGLUNIFORMMATRIX4FVARBPROC glUniformMatrix4fv = nullptr;
-PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation = nullptr;
-PFNGLCOMPRESSEDTEXIMAGE2DPROC glCompressedTexImage2D = nullptr;
-PFNGLGETSHADERIVPROC glGetShaderiv = nullptr;
-PFNGLGETPROGRAMIVPROC glGetProgramiv = nullptr;
-PFNGLDELETESHADERPROC glDeleteShader = nullptr;
-PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays = nullptr;
-PFNGLDELETEBUFFERSPROC glDeleteBuffers = nullptr;
-PFNGLDELETEPROGRAMPROC glDeleteProgram = nullptr;
-PFNGLDETACHSHADERPROC glDetachShader = nullptr;
-PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog = nullptr;
-PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog = nullptr;
-#endif // BA_OSTYPE_WINDOWS
+// #if BA_OSTYPE_ANDROID
+// PFNGLDISCARDFRAMEBUFFEREXTPROC _glDiscardFramebufferEXT = nullptr;
+// #endif
namespace ballistica::base {
-#pragma clang diagnostic push
-#pragma ide diagnostic ignored "hicpp-signed-bitwise"
-#pragma ide diagnostic ignored "EmptyDeclOrStmt"
+bool g_sys_gl_inited{};
-GLContext::GLContext(int target_res_x, int target_res_y, bool fullscreen)
- : fullscreen_(fullscreen) {
- assert(g_base->InGraphicsThread());
- bool need_window = true;
-#if BA_RIFT_BUILD
- // on the rift build we don't need a window when running in vr mode; we just
- // use the context we're created into...
- if (g_core->IsVRMode()) {
- need_window = false;
- }
-#endif // BA_RIFT_BUILD
- if (explicit_bool(need_window)) {
-#if BA_SDL2_BUILD
-#if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
- int flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_BORDERLESS;
-#else
- // Things are a bit more varied on desktop..
- uint32_t flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
- | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE;
- if (fullscreen_) {
- flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
- }
-#endif
- sdl_window_ = SDL_CreateWindow(nullptr, SDL_WINDOWPOS_UNDEFINED,
- SDL_WINDOWPOS_UNDEFINED, target_res_x,
- target_res_y, flags);
- if (!sdl_window_) {
- throw Exception("Unable to create SDL Window of size "
- + std::to_string(target_res_x) + " by "
- + std::to_string(target_res_y));
- }
- sdl_gl_context_ = SDL_GL_CreateContext(sdl_window_);
- if (!sdl_gl_context_) {
- throw Exception("Unable to create SDL GL Context");
- }
- SDL_SetWindowTitle(sdl_window_, "BallisticaKit");
+// #if 0
+// // Fetch needed android gl stuff.
+// #if BA_OSTYPE_ANDROID
+// #define GET(PTRTYPE, FUNC, REQUIRED) \
+// FUNC = (PTRTYPE)eglGetProcAddress(#FUNC); \
+// if (!FUNC) FUNC = (PTRTYPE)eglGetProcAddress(#FUNC "EXT"); \
+// if (REQUIRED) { \
+// BA_PRECONDITION(FUNC != nullptr); \
+// }
+// GET(PFNGLDISCARDFRAMEBUFFEREXTPROC, _glDiscardFramebufferEXT, false);
+// #endif // BA_OSTYPE_ANDROID
- // Our actual drawable size could differ from the window size on retina
- // devices.
- int win_size_x, win_size_y;
- SDL_GetWindowSize(sdl_window_, &win_size_x, &win_size_y);
- AppAdapterSDL::Get()->SetInitialScreenDimensions(Vector2f(
- static_cast(win_size_x), static_cast(win_size_y)));
-#if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
- res_x_ = win_size_x;
- res_y_ = win_size_y;
-#else
- SDL_GL_GetDrawableSize(sdl_window_, &res_x_, &res_y_);
-#endif // BA_OSTYPE_ANDROID
+// #endif // 0
- // This can come through as zero in some cases (on our cardboard build at
- // least).
- if (win_size_x != 0) {
- pixel_density_ =
- static_cast(res_x_) / static_cast(win_size_x);
- }
-#elif BA_SDL_BUILD // BA_SDL2_BUILD
+// Provide an empty implementation of this if noone provided a real one.
+#ifndef BA_HAS_SYS_GL_INIT
- int v_flags;
- v_flags = SDL_OPENGL;
- if (fullscreen_) {
- v_flags |= SDL_FULLSCREEN;
- // convert to the closest valid fullscreen resolution
- // (our last 1.2 build is mac and it's got hacked-in fullscreen-window
- // support; so we don't need this) getValidResolution(target_res_x,
- // target_res_y);
- } else {
- v_flags |= SDL_RESIZABLE;
- }
- surface_ = SDL_SetVideoMode(target_res_x, target_res_y, 32, v_flags);
+void SysGLInit() { assert(!g_sys_gl_inited); }
- // if we failed, fall back to windowed mode.
- if (surface_ == nullptr) {
- throw Exception("SDL_SetVideoMode() failed for "
- + std::to_string(target_res_x) + " by "
- + std::to_string(target_res_y) + " fullscreen="
- + std::to_string(static_cast(fullscreen_)));
- }
- res_x_ = surface_->w;
- res_y_ = surface_->h;
- AppAdapterSDL::Get()->SetInitialScreenDimensions(Vector2f(res_x_, res_y_));
- SDL_WM_SetCaption("BallisticaKit", "BallisticaKit");
-#elif BA_OSTYPE_ANDROID
- // On Android the Java layer creates a GL setup before even calling us.
- // So we have nothing to do here. Hooray!
-#else
- throw Exception("FIXME: Unimplemented");
-#endif // BA_SDL2_BUILD
- }
-
- // Fetch needed android gl stuff.
-#if BA_OSTYPE_ANDROID
-#define GET(PTRTYPE, FUNC, REQUIRED) \
- FUNC = (PTRTYPE)eglGetProcAddress(#FUNC); \
- if (!FUNC) FUNC = (PTRTYPE)eglGetProcAddress(#FUNC "EXT"); \
- if (REQUIRED) { \
- BA_PRECONDITION(FUNC != nullptr); \
- }
- GET(PFNGLDISCARDFRAMEBUFFEREXTPROC, _glDiscardFramebufferEXT, false);
-#endif // BA_OSTYPE_ANDROID
-
- // Fetch needed windows gl stuff.
-#if BA_OSTYPE_WINDOWS
-#define GET(PTRTYPE, FUNC, REQUIRED) \
- FUNC = (PTRTYPE)wglGetProcAddress(#FUNC); \
- if (!FUNC) FUNC = (PTRTYPE)wglGetProcAddress(#FUNC "EXT"); \
- if (REQUIRED) { \
- BA_PRECONDITION(FUNC != nullptr); \
- }
- GET(PFNGLGETINTERNALFORMATIVPROC, glGetInternalformativ,
- false); // for checking msaa level support
- GET(PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC,
- glGetFramebufferAttachmentParameteriv, false); // for checking srgb stuff
- GET(PFNGLBLENDFUNCSEPARATEPROC, glBlendFuncSeparate,
- false); // needed for VR overlay
- GET(PFNGLACTIVETEXTUREPROC, glActiveTexture, true);
- GET(PFNGLCLIENTACTIVETEXTUREARBPROC, glClientActiveTextureARB, true);
- GET(PFNWGLSWAPINTERVALEXTPROC, wglSwapIntervalEXT, true);
- GET(PFNGLPOINTPARAMETERFVARBPROC, glPointParameterfvARB, true);
- GET(PFNGLPOINTPARAMETERFARBPROC, glPointParameterfARB, true);
- GET(PFNGLCREATEPROGRAMPROC, glCreateProgram, true);
- GET(PFNGLCREATESHADERPROC, glCreateShader, true);
- GET(PFNGLSHADERSOURCEPROC, glShaderSource, true);
- GET(PFNGLCOMPILESHADERPROC, glCompileShader, true);
- GET(PFNGLLINKPROGRAMPROC, glLinkProgram, true);
- GET(PFNGLGETINFOLOGARBPROC, glGetInfoLogARB, true);
- GET(PFNGLATTACHSHADERPROC, glAttachShader, true);
- GET(PFNGLUSEPROGRAMOBJECTARBPROC, glUseProgram, true);
- GET(PFNGLGENERATEMIPMAPPROC, glGenerateMipmap, true);
- GET(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer, true);
- GET(PFNGLGETUNIFORMLOCATIONPROC, glGetUniformLocation, true);
- GET(PFNGLUNIFORM1IPROC, glUniform1i, true);
- GET(PFNGLUNIFORM1FPROC, glUniform1f, true);
- GET(PFNGLUNIFORM1FVPROC, glUniform1fv, true);
- GET(PFNGLUNIFORM2FPROC, glUniform2f, true);
- GET(PFNGLUNIFORM3FPROC, glUniform3f, true);
- GET(PFNGLUNIFORM4FPROC, glUniform4f, true);
- GET(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers, true);
- GET(PFNGLGENBUFFERSPROC, glGenBuffers, true);
- GET(PFNGLFRAMEBUFFERTEXTURE2DPROC, glFramebufferTexture2D, true);
- GET(PFNGLGENRENDERBUFFERSPROC, glGenRenderbuffers, true);
- GET(PFNGLBINDRENDERBUFFERPROC, glBindRenderbuffer, true);
- GET(PFNGLBINDBUFFERPROC, glBindBuffer, true);
- GET(PFNGLBUFFERDATAPROC, glBufferData, true);
- GET(PFNGLRENDERBUFFERSTORAGEPROC, glRenderbufferStorage, true);
- GET(PFNGLFRAMEBUFFERRENDERBUFFERPROC, glFramebufferRenderbuffer, true);
- GET(PFNGLCHECKFRAMEBUFFERSTATUSPROC, glCheckFramebufferStatus, true);
- GET(PFNGLDELETEFRAMEBUFFERSPROC, glDeleteFramebuffers, true);
- GET(PFNGLDELETERENDERBUFFERSPROC, glDeleteRenderbuffers, true);
- GET(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer, true);
- GET(PFNGLENABLEVERTEXATTRIBARRAYPROC, glEnableVertexAttribArray, true);
- GET(PFNGLDISABLEVERTEXATTRIBARRAYPROC, glDisableVertexAttribArray, true);
- GET(PFNGLUNIFORMMATRIX4FVARBPROC, glUniformMatrix4fv, true);
- GET(PFNGLBINDATTRIBLOCATIONPROC, glBindAttribLocation, true);
- GET(PFNGLCOMPRESSEDTEXIMAGE2DPROC, glCompressedTexImage2D, true);
- GET(PFNGLGETSHADERIVPROC, glGetShaderiv, true);
- GET(PFNGLGETPROGRAMIVPROC, glGetProgramiv, true);
- GET(PFNGLDELETESHADERPROC, glDeleteShader, true);
- GET(PFNGLDELETEBUFFERSPROC, glDeleteBuffers, true);
- GET(PFNGLDELETEPROGRAMPROC, glDeleteProgram, true);
- GET(PFNGLDETACHSHADERPROC, glDetachShader, true);
- GET(PFNGLGETSHADERINFOLOGPROC, glGetShaderInfoLog, true);
- GET(PFNGLGETPROGRAMINFOLOGPROC, glGetProgramInfoLog, true);
-
- // Stuff we can live without:
- GET(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray, false);
- GET(PFNGLGENVERTEXARRAYSPROC, glGenVertexArrays, false);
- GET(PFNGLDELETEVERTEXARRAYSPROC, glDeleteVertexArrays, false);
- GET(PFNGLBLITFRAMEBUFFERPROC, glBlitFramebuffer, false);
- GET(PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC, glRenderbufferStorageMultisample,
- false);
-
-#undef GET
-#endif // BA_OSTYPE_WINDOWS
-
- // So that our window comes up nice and black.
- // FIXME should just make the window's blanking color black.
-
-#if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
- // Not needed here.
-#else
-
-#if BA_SDL2_BUILD
- // Gonna wait and see if if still need this.
-#elif BA_SDL_BUILD
- glClearColor(0, 0, 0, 1);
- glClear(GL_COLOR_BUFFER_BIT);
- SDL_GL_SwapBuffers();
-#endif // BA_SDL2_BUILD
-
-#endif // IOS/ANDROID
-}
-#pragma clang diagnostic pop
-
-void GLContext::SetVSync(bool enable) {
- assert(g_base->InGraphicsThread());
-
-#if BA_OSTYPE_MACOS
- CGLContextObj context = CGLGetCurrentContext();
- BA_PRECONDITION(context);
- GLint sync = enable;
- CGLSetParameter(context, kCGLCPSwapInterval, &sync);
-#else
-
-#endif // BA_OSTYPE_MACOS
-}
-
-GLContext::~GLContext() {
- if (!g_base->InGraphicsThread()) {
- Log(LogLevel::kError, "GLContext dying in non-graphics thread");
- }
-#if BA_SDL2_BUILD
-
-#if BA_RIFT_BUILD
- // (in rift we only have a window in 2d mode)
- if (!g_core->IsVRMode()) {
- BA_PRECONDITION_LOG(sdl_window_);
- }
-#else // BA_RIFT_MODE
- BA_PRECONDITION_LOG(sdl_window_);
-#endif // BA_RIFT_BUILD
-
- if (sdl_window_) {
- SDL_DestroyWindow(sdl_window_);
- sdl_window_ = nullptr;
- }
-#elif BA_SDL_BUILD
- BA_PRECONDITION_LOG(surface_);
- if (surface_) {
- SDL_FreeSurface(surface_);
- surface_ = nullptr;
- }
-#endif
-}
-
-auto GLErrorToString(GLenum err) -> std::string {
- switch (err) {
- case GL_NO_ERROR:
- return "GL_NO_ERROR";
- case GL_INVALID_ENUM:
- return "GL_INVALID_ENUM";
- case GL_INVALID_VALUE:
- return "GL_INVALID_VALUE";
- case GL_INVALID_OPERATION:
- return "GL_INVALID_OPERATION";
- case GL_OUT_OF_MEMORY:
- return "GL_OUT_OF_MEMORY";
- case GL_INVALID_FRAMEBUFFER_OPERATION:
- return "GL_INVALID_FRAMEBUFFER_OPERATION";
- default:
- return std::to_string(err);
- }
-}
+#endif // BA_HAS_SYS_GL_INIT
} // namespace ballistica::base
diff --git a/src/ballistica/base/graphics/gl/gl_sys.h b/src/ballistica/base/graphics/gl/gl_sys.h
index 968896ab..e2519c70 100644
--- a/src/ballistica/base/graphics/gl/gl_sys.h
+++ b/src/ballistica/base/graphics/gl/gl_sys.h
@@ -3,204 +3,232 @@
#ifndef BALLISTICA_BASE_GRAPHICS_GL_GL_SYS_H_
#define BALLISTICA_BASE_GRAPHICS_GL_GL_SYS_H_
+// A single header to include system GL headers along with custom
+// per-platform defines/function-pointers/etc.
+
#if BA_ENABLE_OPENGL
+// On most platforms we directly link against GL and want all the functions
+// defined in the header for us. On Windows we have to define/load newer
+// stuff manually though, so we don't want that.
#if !BA_OSTYPE_WINDOWS
#define GL_GLEXT_PROTOTYPES
#endif
-#include
+// ----------------------------- BASE GL INCLUDES ------------------------------
-#if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
-
-#if BA_USE_ES3_INCLUDES
-#include
-#include
-#elif BA_OSTYPE_IOS_TVOS
-#include
-#include
-#else
+// On SDL builds, let SDL handle this for us.
#if BA_SDL_BUILD
-#include // needed for ios?...
-#include
-#else
-// FIXME: According to https://developer.android.com/ndk/guides/stable_apis
-// we can always link against ES3.1 now that we're API 21+, so we shouldn't
-// need our funky stubs and function lookups anymore.
-// (though we'll still need to check for availability of 3.x features)
-#include
-#include
-#endif // BA_SDL_BUILD
-#endif // BA_USE_ES3_INCLUDES
-
-// looks like these few defines are currently missing on android
-// (s3tc works on some nvidia hardware)
-#ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT
-#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
-#endif
-#ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
-#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
-#endif
-#ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
-#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
-#endif
-#ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
-#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
-#endif
-
-#else // BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
-
-// SDK Desktop builds.
-#if BA_SDL2_BUILD
#include
-#elif BA_SDL_BUILD // BA_SDL2_BUILD
-#define NO_SDL_GLEXT
-#include
-#endif // BA_SDL2_BUILD
+#endif
-#if BA_OSTYPE_MACOS
+// For XCode builds, grab Apple's framework-y headers.
#if BA_XCODE_BUILD
-#include
-#include
-#include
-#endif // BA_XCODE_BUILD
-#endif // BA_OSTYPE_MACOS
-
-#endif // BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
-
-#include "ballistica/core/platform/support/min_sdl.h"
-#include "ballistica/shared/foundation/object.h"
-
-#if BA_OSTYPE_ANDROID
-extern PFNGLDISCARDFRAMEBUFFEREXTPROC _glDiscardFramebufferEXT;
+#if BA_OPENGL_IS_ES
+#include
+#include
+#else
+#include
+#include
#endif
+#endif
+
+// On Android, we're currently supporting Android API 21 and newer, which
+// means we can count on GL ES 3.1 libs/headers always being available. Note
+// that hardware may still be limited to older versions so we need to check
+// for that and set a limit in our manifest.
+#if BA_OSTYPE_ANDROID
+#include
+#include
+#endif
+
+// -----------------------------------------------------------------------------
+
+// Now mix in a bit of magic of our own...
+
+// We may use S3TC types even on ES (Android Nvidia hardware supports them)
+// but they're not currently in ES's glext.h. Define here if needed.
+#ifndef GL_EXT_texture_compression_s3tc
+#define GL_EXT_texture_compression_s3tc 1
+#define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
+#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
+#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
+#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
+#endif /* GL_EXT_texture_compression_s3tc */
+
+// Anisotropic texturing is still an extension in GL 3 and ES 3.2, so
+// define its values if need be (they seem to exist in desktop glext.h
+// but not es)
+#ifndef GL_EXT_texture_filter_anisotropic
+#define GL_EXT_texture_filter_anisotropic 1
+#define GL_TEXTURE_MAX_ANISOTROPY_EXT 0x84FE
+#define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
+#endif /* GL_EXT_texture_filter_anisotropic */
+
+// Desktop GL has glDepthRange() which takes a double. GL ES has
+// glDepthRangef() which takes a float. Let's always accept doubles and
+// down-convert where needed.
+#if BA_OPENGL_IS_ES
+inline void glDepthRange(double min, double max) {
+ return glDepthRangef(min, max);
+}
+#endif
+
+// #if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
+
+// #if BA_USE_ES3_INCLUDES
+// #include
+// #include
+// #elif BA_OSTYPE_IOS_TVOS
+// #include
+// #include
+// #else
+// #if BA_SDL_BUILD
+// #include // needed for ios?...
+// #include
+// #else
+// // FIXME: According to https://developer.android.com/ndk/guides/stable_apis
+// // we can always link against ES3.1 now that we're API 21+, so we shouldn't
+// // need our funky stubs and function lookups anymore.
+// // (though we'll still need to check for availability of 3.x features)
+// #include
+// #include
+// #endif // BA_SDL_BUILD
+// #endif // BA_USE_ES3_INCLUDES
+
+// Looks like these few defines are currently missing on android (s3tc works
+// on some nvidia hardware).
+// #ifndef GL_COMPRESSED_RGB_S3TC_DXT1_EXT
+// #define GL_COMPRESSED_RGB_S3TC_DXT1_EXT 0x83F0
+// #endif
+// #ifndef GL_COMPRESSED_RGBA_S3TC_DXT1_EXT
+// #define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT 0x83F1
+// #endif
+// #ifndef GL_COMPRESSED_RGBA_S3TC_DXT3_EXT
+// #define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT 0x83F2
+// #endif
+// #ifndef GL_COMPRESSED_RGBA_S3TC_DXT5_EXT
+// #define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT 0x83F3
+// #endif
+
+// #if BA_OSTYPE_IOS_TVOS
+// extern void (*glInvalidateFramebuffer)(GLenum target, GLsizei
+// num_attachments,
+// const GLenum* attachments);
+// #define glDepthRange glDepthRangef
+// #define glGenVertexArrays glGenVertexArraysOES
+// #define glDeleteVertexArrays glDeleteVertexArraysOES
+// #define glBindVertexArray glBindVertexArrayOES
+// #define glClearDepth glClearDepthf
+// #endif // BA_OSTYPE_IOS_TVOS
+
+// #else // BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
+
+// SDL Desktop builds.
+// #if BA_SDL2_BUILD
+// #include
+// #elif BA_SDL_BUILD // BA_SDL2_BUILD
+// #define NO_SDL_GLEXT
+// #include
+// #endif // BA_SDL2_BUILD
+
+// #if BA_OSTYPE_MACOS
+// #include
+// (NO LONGER APPLIES IN CORE PROFILE)
+// #define glGenVertexArrays glGenVertexArraysAPPLE
+// #define glDeleteVertexArrays glDeleteVertexArraysAPPLE
+// #define glBindVertexArray glBindVertexArrayAPPLE
+// #endif // BA_OSTYPE_MACOS
+
+// #endif // BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
+
+// #if BA_OSTYPE_ANDROID
+// #include
+// #include
+// #if !BA_USE_ES3_INCLUDES
+// #include "ballistica/core/platform/android/android_gl3.h"
+// #endif
+// #define glDepthRange glDepthRangef
+// #define glDiscardFramebufferEXT _glDiscardFramebufferEXT
+// #ifndef GL_RGB565_OES
+// #define GL_RGB565_OES 0x8D62
+// #endif // GL_RGB565_OES
+// #define GL_READ_FRAMEBUFFER 0x8CA8
+// #define GL_DRAW_FRAMEBUFFER 0x8CA9
+// #define GL_READ_FRAMEBUFFER_BINDING 0x8CAA
+// #define glClearDepth glClearDepthf
+// #endif // BA_OSTYPE_ANDROID
+
+// #if BA_OSTYPE_ANDROID
+// extern PFNGLDISCARDFRAMEBUFFEREXTPROC _glDiscardFramebufferEXT;
+// #endif
#if BA_OSTYPE_WINDOWS
-#ifndef WGL_EXT_swap_control
-#define WGL_EXT_swap_control 1
-typedef BOOL(WINAPI* PFNWGLSWAPINTERVALEXTPROC)(int interval);
-typedef int(WINAPI* PFNWGLGETSWAPINTERVALEXTPROC)(VOID); // NOLINT
-#endif // WGL_EXT_swap_control
-extern PFNGLGETINTERNALFORMATIVPROC glGetInternalformativ;
-extern PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC
- glGetFramebufferAttachmentParameteriv;
-extern PFNGLBLENDFUNCSEPARATEPROC glBlendFuncSeparate;
-extern PFNGLACTIVETEXTUREPROC glActiveTexture;
-extern PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB;
-extern PFNGLPOINTPARAMETERFARBPROC glPointParameterfARB;
-extern PFNGLPOINTPARAMETERFVARBPROC glPointParameterfvARB;
-extern PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT;
-extern PFNGLCREATEPROGRAMPROC glCreateProgram;
-extern PFNGLCREATESHADERPROC glCreateShader;
-extern PFNGLSHADERSOURCEPROC glShaderSource;
-extern PFNGLCOMPILESHADERPROC glCompileShader;
-extern PFNGLLINKPROGRAMPROC glLinkProgram;
-extern PFNGLGETINFOLOGARBPROC glGetInfoLogARB;
-extern PFNGLATTACHSHADERPROC glAttachShader;
-extern PFNGLUSEPROGRAMOBJECTARBPROC glUseProgram;
-extern PFNGLGENERATEMIPMAPPROC glGenerateMipmap;
-extern PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer;
-extern PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer;
-extern PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
-extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
-extern PFNGLUNIFORM1IPROC glUniform1i;
-extern PFNGLUNIFORM1FPROC glUniform1f;
-extern PFNGLUNIFORM1FVPROC glUniform1fv;
-extern PFNGLUNIFORM2FPROC glUniform2f;
-extern PFNGLUNIFORM3FPROC glUniform3f;
-extern PFNGLUNIFORM4FPROC glUniform4f;
-extern PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers;
-extern PFNGLGENBUFFERSPROC glGenBuffers;
-extern PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
-extern PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D;
-extern PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers;
-extern PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer;
-extern PFNGLBINDBUFFERPROC glBindBuffer;
-extern PFNGLBUFFERDATAPROC glBufferData;
-extern PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage;
-extern PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glRenderbufferStorageMultisample;
-extern PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer;
-extern PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus;
-extern PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers;
-extern PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers;
-extern PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;
-extern PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;
-extern PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray;
-extern PFNGLUNIFORMMATRIX4FVARBPROC glUniformMatrix4fv;
-extern PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation;
-extern PFNGLCOMPRESSEDTEXIMAGE2DPROC glCompressedTexImage2D;
-extern PFNGLGETSHADERIVPROC glGetShaderiv;
-extern PFNGLGETPROGRAMIVPROC glGetProgramiv;
-extern PFNGLDELETESHADERPROC glDeleteShader;
-extern PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
-extern PFNGLDELETEBUFFERSPROC glDeleteBuffers;
-extern PFNGLDELETEPROGRAMPROC glDeleteProgram;
-extern PFNGLDETACHSHADERPROC glDetachShader;
-extern PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
-extern PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
-#endif // BA_OSTYPE_WINDOWS
-
-#ifndef GL_NV_texture_rectangle
-#define GL_TEXTURE_RECTANGLE_NV 0x84F5
-#define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6
-#define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7
-#define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8
-#endif
-#ifndef GL_NV_texture_rectangle
-#define GL_NV_texture_rectangle 1
+#include "ballistica/base/graphics/gl/gl_sys_windows.h"
#endif
-// Support for gl object debug labeling.
+// #ifndef GL_NV_texture_rectangle
+// #define GL_TEXTURE_RECTANGLE_NV 0x84F5
+// #define GL_TEXTURE_BINDING_RECTANGLE_NV 0x84F6
+// #define GL_PROXY_TEXTURE_RECTANGLE_NV 0x84F7
+// #define GL_MAX_RECTANGLE_TEXTURE_SIZE_NV 0x84F8
+// #endif
+// #ifndef GL_NV_texture_rectangle
+// #define GL_NV_texture_rectangle 1
+// #endif
+
+// Support for GL object debug labeling.
#if BA_OSTYPE_IOS_TVOS
-#define GL_LABEL_OBJECT(type, obj, label) glLabelObjectEXT(type, obj, 0, label)
-#define GL_PUSH_GROUP_MARKER(label) glPushGroupMarkerEXT(0, label)
-#define GL_POP_GROUP_MARKER() glPopGroupMarkerEXT()
+#define BA_GL_LABEL_OBJECT(type, obj, label) \
+ glLabelObjectEXT(type, obj, 0, label)
+#define BA_GL_PUSH_GROUP_MARKER(label) glPushGroupMarkerEXT(0, label)
+#define BA_GL_POP_GROUP_MARKER() glPopGroupMarkerEXT()
#else
-#define GL_LABEL_OBJECT(type, obj, label) ((void)0)
-#define GL_PUSH_GROUP_MARKER(label) ((void)0)
-#define GL_POP_GROUP_MARKER() ((void)0)
+#define BA_GL_LABEL_OBJECT(type, obj, label) ((void)0)
+#define BA_GL_PUSH_GROUP_MARKER(label) ((void)0)
+#define BA_GL_POP_GROUP_MARKER() ((void)0)
+#endif
+
+// OpenGL ES uses precision; regular GL doesn't.
+#if BA_OPENGL_IS_ES
+#define BA_GLSL_LOWP "lowp "
+#define BA_GLSL_MEDIUMP "mediump "
+#define BA_GLSL_HIGHP "highp "
+#else
+#define BA_GLSL_LOWP
+#define BA_GLSL_MEDIUMP
+#define BA_GLSL_HIGHP
+#endif // BA_OPENGL_IS_ES
+
+// Our old GLSL source uses 'attribute' and our newer uses 'in'
+#if BA_OPENGL_IS_ES
+#define BA_GLSL_VERTEX_IN "attribute"
+#define BA_GLSL_VERTEX_OUT "varying"
+#define BA_GLSL_FRAG_IN "varying"
+#define BA_GLSL_FRAGCOLOR "gl_FragColor"
+#define BA_GLSL_TEXTURE2D "texture2D"
+#define BA_GLSL_TEXTURE2DPROJ "texture2DProj"
+#define BA_GLSL_TEXTURECUBE "textureCube"
+#else
+#define BA_GLSL_VERTEX_IN "in"
+#define BA_GLSL_VERTEX_OUT "out"
+#define BA_GLSL_FRAG_IN "in"
+#define BA_GLSL_FRAGCOLOR "fragColor"
+#define BA_GLSL_TEXTURE2D "texture"
+#define BA_GLSL_TEXTURE2DPROJ "textureProj"
+#define BA_GLSL_TEXTURECUBE "texture"
#endif
namespace ballistica::base {
-auto GLErrorToString(GLenum err) -> std::string;
+extern bool g_sys_gl_inited;
-// Container for OpenGL rendering context data.
-class GLContext {
- public:
- GLContext(int target_res_x, int target_res_y, bool fullScreen);
- ~GLContext();
- auto res_x() const -> int { return res_x_; }
- auto res_y() const -> int { return res_y_; }
- auto pixel_density() const -> float { return pixel_density_; }
- void SetVSync(bool enable);
-
- // Currently no surface/window in this case.
-#if BA_SDL2_BUILD
- auto sdl_window() const -> SDL_Window* {
- assert(sdl_window_);
- return sdl_window_;
- }
-#elif BA_SDL_BUILD // BA_SDL2_BUILD
- SDL_Surface* sdl_screen_surface() const {
- assert(surface_);
- return surface_;
- }
-#endif // BA_SDL2_BUILD
-
- private:
-#if BA_SDL2_BUILD
- SDL_Window* sdl_window_{};
- SDL_GLContext sdl_gl_context_{};
-#endif // BA_SDL2_BUILD
- bool fullscreen_{};
- int res_x_{};
- int res_y_{};
- float pixel_density_{1.0f};
-#if BA_SDL_BUILD && !BA_SDL2_BUILD
- SDL_Surface* surface_{};
-#endif
-}; // GLContext
+// Called when a GL renderer is spinning up. Allows fetching/assigning any
+// global function pointers or data needed for GL to function. Will be
+// called only once and then g_sys_gl_inited set. A platform that defines
+// this should define BA_HAS_SYS_GL_INIT; otherwise a default empty
+// implementation will be defined.
+void SysGLInit();
} // namespace ballistica::base
diff --git a/src/ballistica/base/graphics/gl/gl_sys_windows.cc b/src/ballistica/base/graphics/gl/gl_sys_windows.cc
new file mode 100644
index 00000000..f627bcd1
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/gl_sys_windows.cc
@@ -0,0 +1,175 @@
+// Released under the MIT License. See LICENSE for details.
+
+#if BA_ENABLE_OPENGL && BA_OSTYPE_WINDOWS
+#include "ballistica/base/graphics/gl/gl_sys_windows.h"
+
+#include "SDL.h"
+#include "ballistica/base/graphics/gl/gl_sys.h"
+#include "ballistica/shared/ballistica.h"
+
+#pragma comment(lib, "opengl32.lib")
+// #pragma comment(lib, "glu32.lib")
+
+PFNGLGETINTERNALFORMATIVPROC glGetInternalformativ{};
+PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC
+glGetFramebufferAttachmentParameteriv{};
+PFNGLBLENDFUNCSEPARATEPROC glBlendFuncSeparate{};
+PFNGLACTIVETEXTUREPROC glActiveTextureBA{};
+// PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB{};
+PFNGLPOINTPARAMETERFVARBPROC glPointParameterfvARB{};
+// PFNGLPOINTPARAMETERFARBPROC glPointParameterfARB{};
+PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT{};
+PFNGLCREATEPROGRAMPROC glCreateProgram{};
+PFNGLCREATESHADERPROC glCreateShader{};
+PFNGLSHADERSOURCEPROC glShaderSource{};
+PFNGLCOMPILESHADERPROC glCompileShader{};
+PFNGLLINKPROGRAMPROC glLinkProgram{};
+PFNGLGETINFOLOGARBPROC glGetInfoLogARB{};
+PFNGLATTACHSHADERPROC glAttachShader{};
+PFNGLUSEPROGRAMOBJECTARBPROC glUseProgram{};
+PFNGLGENERATEMIPMAPPROC glGenerateMipmap{};
+PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer{};
+PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer{};
+PFNGLBINDVERTEXARRAYPROC glBindVertexArray{};
+PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation{};
+PFNGLUNIFORM1IPROC glUniform1i{};
+PFNGLUNIFORM1FPROC glUniform1f{};
+PFNGLUNIFORM1FVPROC glUniform1fv{};
+PFNGLUNIFORM2FPROC glUniform2f{};
+PFNGLUNIFORM3FPROC glUniform3f{};
+PFNGLUNIFORM4FPROC glUniform4f{};
+PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers{};
+PFNGLGENBUFFERSPROC glGenBuffers{};
+PFNGLGENVERTEXARRAYSPROC glGenVertexArrays{};
+PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D{};
+PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers{};
+PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer{};
+PFNGLBINDBUFFERPROC glBindBuffer{};
+PFNGLBUFFERDATAPROC glBufferData{};
+PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage{};
+PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glRenderbufferStorageMultisample{};
+PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer{};
+PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus{};
+PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers{};
+PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers{};
+PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer{};
+PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray{};
+PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray{};
+PFNGLUNIFORMMATRIX4FVARBPROC glUniformMatrix4fv{};
+PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation{};
+PFNGLCOMPRESSEDTEXIMAGE2DPROC glCompressedTexImage2DBA{};
+PFNGLGETSHADERIVPROC glGetShaderiv{};
+PFNGLGETPROGRAMIVPROC glGetProgramiv{};
+PFNGLDELETESHADERPROC glDeleteShader{};
+PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays{};
+PFNGLDELETEBUFFERSPROC glDeleteBuffers{};
+PFNGLDELETEPROGRAMPROC glDeleteProgram{};
+PFNGLDETACHSHADERPROC glDetachShader{};
+PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog{};
+PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog{};
+PFNGLGETSTRINGIPROC glGetStringi{};
+
+namespace ballistica::base {
+
+static auto GetGLFunc_(const char* name, bool required) -> void* {
+ void* func = SDL_GL_GetProcAddress(name);
+ if (!func) {
+ func = SDL_GL_GetProcAddress((std::string(name) + "EXT").c_str());
+ }
+ if (required && func == nullptr) {
+ FatalError("OpenGL function '" + std::string(name)
+ + "' not found.\nAre your graphics drivers up to date?");
+ }
+ return func;
+}
+
+// Our variable name matches the name we're fetching from the library.
+#define GET(PTRTYPE, FUNC, REQUIRED) FUNC = (PTRTYPE)GetGLFunc_(#FUNC, REQUIRED)
+
+// Our variable name equals the library name + BA. (For symbol clashes).
+#define GET2(PTRTYPE, FUNC, REQUIRED) \
+ FUNC##BA = (PTRTYPE)GetGLFunc_(#FUNC, REQUIRED)
+
+void SysGLInit() {
+ assert(!g_sys_gl_inited);
+
+ SDL_GL_LoadLibrary(nullptr);
+
+ void* testval{};
+
+ PFNGLGETINTERNALFORMATIVPROC fptr;
+ fptr = (PFNGLGETINTERNALFORMATIVPROC)testval;
+
+ // For checking msaa level support. This is only available in GL 4.2+
+ // so we can survive without it.
+ GET(PFNGLGETINTERNALFORMATIVPROC, glGetInternalformativ, false);
+
+ // For checking srgb stuff.
+ GET(PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC,
+ glGetFramebufferAttachmentParameteriv, false);
+
+ // Needed for VR overlay.
+ GET(PFNGLBLENDFUNCSEPARATEPROC, glBlendFuncSeparate, false);
+
+ GET2(PFNGLACTIVETEXTUREPROC, glActiveTexture, true);
+ // GET(PFNGLCLIENTACTIVETEXTUREARBPROC, glClientActiveTextureARB, true);
+ GET(PFNWGLSWAPINTERVALEXTPROC, wglSwapIntervalEXT, true);
+ GET(PFNGLPOINTPARAMETERFVARBPROC, glPointParameterfvARB, true);
+ // GET(PFNGLPOINTPARAMETERFARBPROC, glPointParameterfARB, true);
+ GET(PFNGLCREATEPROGRAMPROC, glCreateProgram, true);
+ GET(PFNGLCREATESHADERPROC, glCreateShader, true);
+ GET(PFNGLSHADERSOURCEPROC, glShaderSource, true);
+ GET(PFNGLCOMPILESHADERPROC, glCompileShader, true);
+ GET(PFNGLLINKPROGRAMPROC, glLinkProgram, true);
+ GET(PFNGLGETINFOLOGARBPROC, glGetInfoLogARB, true);
+ GET(PFNGLATTACHSHADERPROC, glAttachShader, true);
+ GET(PFNGLUSEPROGRAMOBJECTARBPROC, glUseProgram, true);
+ GET(PFNGLGENERATEMIPMAPPROC, glGenerateMipmap, true);
+ GET(PFNGLBINDFRAMEBUFFERPROC, glBindFramebuffer, true);
+ GET(PFNGLGETUNIFORMLOCATIONPROC, glGetUniformLocation, true);
+ GET(PFNGLUNIFORM1IPROC, glUniform1i, true);
+ GET(PFNGLUNIFORM1FPROC, glUniform1f, true);
+ GET(PFNGLUNIFORM1FVPROC, glUniform1fv, true);
+ GET(PFNGLUNIFORM2FPROC, glUniform2f, true);
+ GET(PFNGLUNIFORM3FPROC, glUniform3f, true);
+ GET(PFNGLUNIFORM4FPROC, glUniform4f, true);
+ GET(PFNGLGENFRAMEBUFFERSPROC, glGenFramebuffers, true);
+ GET(PFNGLGENBUFFERSPROC, glGenBuffers, true);
+ GET(PFNGLFRAMEBUFFERTEXTURE2DPROC, glFramebufferTexture2D, true);
+ GET(PFNGLGENRENDERBUFFERSPROC, glGenRenderbuffers, true);
+ GET(PFNGLBINDRENDERBUFFERPROC, glBindRenderbuffer, true);
+ GET(PFNGLBINDBUFFERPROC, glBindBuffer, true);
+ GET(PFNGLBUFFERDATAPROC, glBufferData, true);
+ GET(PFNGLRENDERBUFFERSTORAGEPROC, glRenderbufferStorage, true);
+ GET(PFNGLFRAMEBUFFERRENDERBUFFERPROC, glFramebufferRenderbuffer, true);
+ GET(PFNGLCHECKFRAMEBUFFERSTATUSPROC, glCheckFramebufferStatus, true);
+ GET(PFNGLDELETEFRAMEBUFFERSPROC, glDeleteFramebuffers, true);
+ GET(PFNGLDELETERENDERBUFFERSPROC, glDeleteRenderbuffers, true);
+ GET(PFNGLVERTEXATTRIBPOINTERPROC, glVertexAttribPointer, true);
+ GET(PFNGLENABLEVERTEXATTRIBARRAYPROC, glEnableVertexAttribArray, true);
+ GET(PFNGLDISABLEVERTEXATTRIBARRAYPROC, glDisableVertexAttribArray, true);
+ GET(PFNGLUNIFORMMATRIX4FVARBPROC, glUniformMatrix4fv, true);
+ GET(PFNGLBINDATTRIBLOCATIONPROC, glBindAttribLocation, true);
+ GET2(PFNGLCOMPRESSEDTEXIMAGE2DPROC, glCompressedTexImage2D, true);
+ GET(PFNGLGETSHADERIVPROC, glGetShaderiv, true);
+ GET(PFNGLGETPROGRAMIVPROC, glGetProgramiv, true);
+ GET(PFNGLDELETESHADERPROC, glDeleteShader, true);
+ GET(PFNGLDELETEBUFFERSPROC, glDeleteBuffers, true);
+ GET(PFNGLDELETEPROGRAMPROC, glDeleteProgram, true);
+ GET(PFNGLDETACHSHADERPROC, glDetachShader, true);
+ GET(PFNGLGETSHADERINFOLOGPROC, glGetShaderInfoLog, true);
+ GET(PFNGLGETPROGRAMINFOLOGPROC, glGetProgramInfoLog, true);
+ GET(PFNGLGETSTRINGIPROC, glGetStringi, true);
+ GET(PFNGLBINDVERTEXARRAYPROC, glBindVertexArray, true);
+ GET(PFNGLGENVERTEXARRAYSPROC, glGenVertexArrays, true);
+ GET(PFNGLDELETEVERTEXARRAYSPROC, glDeleteVertexArrays, true);
+ GET(PFNGLBLITFRAMEBUFFERPROC, glBlitFramebuffer, true);
+ GET(PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC, glRenderbufferStorageMultisample,
+ true);
+}
+#undef GET
+#undef GET2
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL && BA_OSTYPE_WINDOWS
diff --git a/src/ballistica/base/graphics/gl/gl_sys_windows.h b/src/ballistica/base/graphics/gl/gl_sys_windows.h
new file mode 100644
index 00000000..228543aa
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/gl_sys_windows.h
@@ -0,0 +1,91 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_GL_SYS_WINDOWS_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_GL_SYS_WINDOWS_H_
+
+// System GL bits for windows.
+
+#if BA_ENABLE_OPENGL && BA_OSTYPE_WINDOWS
+
+// We don't *actually* need this because gl_sys.h includes it before
+// it includes us, but this keeps things from erroring if we look at
+// the header by itself.
+#include
+
+// We run some init code to grab function ptrs/etc.
+#define BA_HAS_SYS_GL_INIT
+
+#ifndef WGL_EXT_swap_control
+#define WGL_EXT_swap_control 1
+typedef BOOL(WINAPI* PFNWGLSWAPINTERVALEXTPROC)(int interval);
+typedef int(WINAPI* PFNWGLGETSWAPINTERVALEXTPROC)(VOID); // NOLINT
+#endif
+
+// These seem to be defined by the SDL GL headers even though we asked them
+// nicely not to (by not defining GL_GLEXT_PROTOTYPES). So we need to import
+// and use it via a custom name.
+#define glActiveTexture glActiveTextureBA
+#define glCompressedTexImage2D glCompressedTexImage2DBA
+
+extern PFNGLGETINTERNALFORMATIVPROC glGetInternalformativ;
+extern PFNGLGETFRAMEBUFFERATTACHMENTPARAMETERIVPROC
+ glGetFramebufferAttachmentParameteriv;
+extern PFNGLBLENDFUNCSEPARATEPROC glBlendFuncSeparate;
+extern PFNGLACTIVETEXTUREPROC glActiveTextureBA;
+// extern PFNGLCLIENTACTIVETEXTUREARBPROC glClientActiveTextureARB;
+// extern PFNGLPOINTPARAMETERFARBPROC glPointParameterfARB;
+extern PFNGLPOINTPARAMETERFVARBPROC glPointParameterfvARB;
+extern PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT;
+extern PFNGLCREATEPROGRAMPROC glCreateProgram;
+extern PFNGLCREATESHADERPROC glCreateShader;
+extern PFNGLSHADERSOURCEPROC glShaderSource;
+extern PFNGLCOMPILESHADERPROC glCompileShader;
+extern PFNGLLINKPROGRAMPROC glLinkProgram;
+extern PFNGLGETINFOLOGARBPROC glGetInfoLogARB;
+extern PFNGLATTACHSHADERPROC glAttachShader;
+extern PFNGLUSEPROGRAMOBJECTARBPROC glUseProgram;
+extern PFNGLGENERATEMIPMAPPROC glGenerateMipmap;
+extern PFNGLBINDFRAMEBUFFERPROC glBindFramebuffer;
+extern PFNGLBLITFRAMEBUFFERPROC glBlitFramebuffer;
+extern PFNGLBINDVERTEXARRAYPROC glBindVertexArray;
+extern PFNGLGETUNIFORMLOCATIONPROC glGetUniformLocation;
+extern PFNGLUNIFORM1IPROC glUniform1i;
+extern PFNGLUNIFORM1FPROC glUniform1f;
+extern PFNGLUNIFORM1FVPROC glUniform1fv;
+extern PFNGLUNIFORM2FPROC glUniform2f;
+extern PFNGLUNIFORM3FPROC glUniform3f;
+extern PFNGLUNIFORM4FPROC glUniform4f;
+extern PFNGLGENFRAMEBUFFERSPROC glGenFramebuffers;
+extern PFNGLGENBUFFERSPROC glGenBuffers;
+extern PFNGLGENVERTEXARRAYSPROC glGenVertexArrays;
+extern PFNGLFRAMEBUFFERTEXTURE2DPROC glFramebufferTexture2D;
+extern PFNGLGENRENDERBUFFERSPROC glGenRenderbuffers;
+extern PFNGLBINDRENDERBUFFERPROC glBindRenderbuffer;
+extern PFNGLBINDBUFFERPROC glBindBuffer;
+extern PFNGLBUFFERDATAPROC glBufferData;
+extern PFNGLRENDERBUFFERSTORAGEPROC glRenderbufferStorage;
+extern PFNGLRENDERBUFFERSTORAGEMULTISAMPLEPROC glRenderbufferStorageMultisample;
+extern PFNGLFRAMEBUFFERRENDERBUFFERPROC glFramebufferRenderbuffer;
+extern PFNGLCHECKFRAMEBUFFERSTATUSPROC glCheckFramebufferStatus;
+extern PFNGLDELETEFRAMEBUFFERSPROC glDeleteFramebuffers;
+extern PFNGLDELETERENDERBUFFERSPROC glDeleteRenderbuffers;
+extern PFNGLVERTEXATTRIBPOINTERPROC glVertexAttribPointer;
+extern PFNGLENABLEVERTEXATTRIBARRAYPROC glEnableVertexAttribArray;
+extern PFNGLDISABLEVERTEXATTRIBARRAYPROC glDisableVertexAttribArray;
+extern PFNGLUNIFORMMATRIX4FVARBPROC glUniformMatrix4fv;
+extern PFNGLBINDATTRIBLOCATIONPROC glBindAttribLocation;
+extern PFNGLCOMPRESSEDTEXIMAGE2DPROC glCompressedTexImage2DBA;
+extern PFNGLGETSHADERIVPROC glGetShaderiv;
+extern PFNGLGETPROGRAMIVPROC glGetProgramiv;
+extern PFNGLDELETESHADERPROC glDeleteShader;
+extern PFNGLDELETEVERTEXARRAYSPROC glDeleteVertexArrays;
+extern PFNGLDELETEBUFFERSPROC glDeleteBuffers;
+extern PFNGLDELETEPROGRAMPROC glDeleteProgram;
+extern PFNGLDETACHSHADERPROC glDetachShader;
+extern PFNGLGETSHADERINFOLOGPROC glGetShaderInfoLog;
+extern PFNGLGETPROGRAMINFOLOGPROC glGetProgramInfoLog;
+extern PFNGLGETSTRINGIPROC glGetStringi;
+
+#endif // BA_ENABLE_OPENGL && BA_OSTYPE_WINDOWS
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_GL_SYS_WINDOWS_H_
diff --git a/src/ballistica/base/graphics/gl/mesh/mesh_asset_data_gl.h b/src/ballistica/base/graphics/gl/mesh/mesh_asset_data_gl.h
new file mode 100644
index 00000000..5fd6936c
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/mesh/mesh_asset_data_gl.h
@@ -0,0 +1,159 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_ASSET_DATA_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_ASSET_DATA_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/gl_sys.h"
+#include "ballistica/base/graphics/gl/renderer_gl.h"
+#include "ballistica/base/graphics/graphics_server.h"
+#include "ballistica/base/graphics/mesh/mesh_renderer_data.h"
+#include "ballistica/shared/ballistica.h"
+
+namespace ballistica::base {
+
+class RendererGL::MeshAssetDataGL : public MeshAssetRendererData {
+ public:
+ enum BufferType { kVertices, kIndices, kBufferCount };
+
+ MeshAssetDataGL(const MeshAsset& model, RendererGL* renderer)
+ : renderer_(renderer), fake_vao_(nullptr) {
+#if BA_DEBUG_BUILD
+ name_ = model.GetName();
+#endif
+
+ assert(g_base->InGraphicsThread());
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ // Create our vertex array to hold all this state.
+
+ glGenVertexArrays(1, &vao_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ renderer->BindVertexArray_(vao_);
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ glGenBuffers(kBufferCount, vbos_);
+
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ // Fill our vertex data buffer.
+ renderer_->BindArrayBuffer(vbos_[kVertices]);
+ BA_DEBUG_CHECK_GL_ERROR;
+ glBufferData(GL_ARRAY_BUFFER,
+ static_cast_check_fit(model.vertices().size()
+ * sizeof(VertexObjectFull)),
+ &(model.vertices()[0]), GL_STATIC_DRAW);
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ glVertexAttribPointer(
+ kVertexAttrPosition, 3, GL_FLOAT, GL_FALSE, sizeof(VertexObjectFull),
+ reinterpret_cast(offsetof(VertexObjectFull, position)));
+ glEnableVertexAttribArray(kVertexAttrPosition);
+ glVertexAttribPointer(
+ kVertexAttrUV, 2, GL_UNSIGNED_SHORT, GL_TRUE, sizeof(VertexObjectFull),
+ reinterpret_cast(offsetof(VertexObjectFull, uv)));
+ glEnableVertexAttribArray(kVertexAttrUV);
+ glVertexAttribPointer(
+ kVertexAttrNormal, 3, GL_SHORT, GL_TRUE, sizeof(VertexObjectFull),
+ reinterpret_cast(offsetof(VertexObjectFull, normal)));
+ glEnableVertexAttribArray(kVertexAttrNormal);
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ // Fill our index data buffer.
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos_[kIndices]);
+
+ const GLvoid* index_data;
+ switch (model.GetIndexSize()) {
+ case 1: {
+ elem_count_ = static_cast(model.indices8().size());
+ index_type_ = GL_UNSIGNED_BYTE;
+ index_data = static_cast(model.indices8().data());
+ break;
+ }
+ case 2: {
+ elem_count_ = static_cast(model.indices16().size());
+ index_type_ = GL_UNSIGNED_SHORT;
+ index_data = static_cast(model.indices16().data());
+ break;
+ }
+ case 4: {
+ BA_LOG_ONCE(
+ LogLevel::kWarning,
+ "GL WARNING - USING 32 BIT INDICES WHICH WONT WORK IN ES2!!");
+ elem_count_ = static_cast(model.indices32().size());
+ index_type_ = GL_UNSIGNED_INT;
+ index_data = static_cast(model.indices32().data());
+ break;
+ }
+ default:
+ throw Exception();
+ }
+ glBufferData(
+ GL_ELEMENT_ARRAY_BUFFER,
+ static_cast_check_fit(elem_count_ * model.GetIndexSize()),
+ index_data, GL_STATIC_DRAW);
+
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+
+ ~MeshAssetDataGL() override {
+ assert(g_base->InGraphicsThread());
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ // Unbind if we're bound; otherwise if a new vao pops up with our same
+ // ID it'd be prevented from binding.
+ if (vao_ == renderer_->current_vertex_array_) {
+ renderer_->BindVertexArray_(0);
+ }
+ if (!g_base->graphics_server->renderer_context_lost()) {
+ glDeleteVertexArrays(1, &vao_);
+ }
+
+ // Make sure our dying buffer isn't current (don't wanna prevent binding
+ // to a new buffer with a recycled id).
+ for (unsigned int vbo : vbos_) {
+ if (vbo == renderer_->active_array_buffer_) {
+ renderer_->active_array_buffer_ = -1;
+ }
+ }
+ if (!g_base->graphics_server->renderer_context_lost()) {
+ glDeleteBuffers(kBufferCount, vbos_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+ }
+
+ void Bind() {
+ renderer_->BindVertexArray_(vao_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+ void Draw() {
+ BA_DEBUG_CHECK_GL_ERROR;
+ if (elem_count_ > 0) {
+ glDrawElements(GL_TRIANGLES, elem_count_, index_type_, nullptr);
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+
+#if BA_DEBUG_BUILD
+ auto name() const -> const std::string& { return name_; }
+#endif
+
+ private:
+#if BA_DEBUG_BUILD
+ std::string name_;
+#endif
+
+ RendererGL* renderer_{};
+ uint32_t elem_count_{};
+ GLuint index_type_{};
+ GLuint vao_{};
+ GLuint vbos_[kBufferCount]{};
+ FakeVertexArrayObject* fake_vao_{};
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_ASSET_DATA_GL_H_
diff --git a/src/ballistica/base/graphics/gl/mesh/mesh_data_dual_texture_full_gl.h b/src/ballistica/base/graphics/gl/mesh/mesh_data_dual_texture_full_gl.h
new file mode 100644
index 00000000..16f158b1
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/mesh/mesh_data_dual_texture_full_gl.h
@@ -0,0 +1,44 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_DUAL_TEXTURE_FULL_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_DUAL_TEXTURE_FULL_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/mesh/mesh_data_gl.h"
+
+namespace ballistica::base {
+
+class RendererGL::MeshDataDualTextureFullGL : public RendererGL::MeshDataGL {
+ public:
+ explicit MeshDataDualTextureFullGL(RendererGL* renderer)
+ : MeshDataGL(renderer, kUsesIndexBuffer) {
+ // Set up our vertex data.
+ renderer_->BindArrayBuffer(vbos_[kVertexBufferPrimary]);
+ glVertexAttribPointer(
+ kVertexAttrUV, 2, GL_UNSIGNED_SHORT, GL_TRUE,
+ sizeof(VertexDualTextureFull),
+ reinterpret_cast(offsetof(VertexDualTextureFull, uv)));
+ glEnableVertexAttribArray(kVertexAttrUV);
+ glVertexAttribPointer(
+ kVertexAttrUV2, 2, GL_UNSIGNED_SHORT, GL_TRUE,
+ sizeof(VertexDualTextureFull),
+ reinterpret_cast(offsetof(VertexDualTextureFull, uv2)));
+ glEnableVertexAttribArray(kVertexAttrUV2);
+ glVertexAttribPointer(
+ kVertexAttrPosition, 3, GL_FLOAT, GL_FALSE,
+ sizeof(VertexDualTextureFull),
+ reinterpret_cast(offsetof(VertexDualTextureFull, position)));
+ glEnableVertexAttribArray(kVertexAttrPosition);
+ }
+ void SetData(MeshBuffer* data) {
+ UpdateBufferData(kVertexBufferPrimary, data, &primary_state_,
+ &have_primary_data_,
+ dynamic_draw_ ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
+ }
+};
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_DUAL_TEXTURE_FULL_GL_H_
diff --git a/src/ballistica/base/graphics/gl/mesh/mesh_data_gl.h b/src/ballistica/base/graphics/gl/mesh/mesh_data_gl.h
new file mode 100644
index 00000000..eb29ceae
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/mesh/mesh_data_gl.h
@@ -0,0 +1,212 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/graphics_server.h"
+
+namespace ballistica::base {
+
+class RendererGL::MeshDataGL : public MeshRendererData {
+ public:
+ enum BufferType {
+ kVertexBufferPrimary,
+ kIndexBuffer,
+ kVertexBufferSecondary
+ };
+
+ enum Flags {
+ kUsesIndexBuffer = 1u,
+ kUsesSecondaryBuffer = 1u << 1u,
+ kUsesDynamicDraw = 1u << 2u
+ };
+
+ MeshDataGL(RendererGL* renderer, uint32_t flags)
+ : renderer_(renderer),
+ uses_secondary_data_(static_cast(flags & kUsesSecondaryBuffer)),
+ uses_index_data_(static_cast(flags & kUsesIndexBuffer)) {
+ assert(g_base->InGraphicsThread());
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ // Create our vertex array to hold all this state.
+
+ glGenVertexArrays(1, &vao_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ renderer->BindVertexArray_(vao_);
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ glGenBuffers(GetBufferCount(), vbos_);
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ if (uses_index_data_) {
+ renderer_->BindVertexArray_(vao_);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vbos_[kIndexBuffer]);
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+
+ auto uses_index_data() const -> bool { return uses_index_data_; }
+
+ // Set us up to be recycled.
+ void Reset() {
+ index_state_ = primary_state_ = secondary_state_ = 0;
+ have_index_data_ = have_secondary_data_ = have_primary_data_ = false;
+ }
+
+ void Bind() {
+ renderer_->BindVertexArray_(vao_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+
+ void Draw(DrawType draw_type) {
+ BA_DEBUG_CHECK_GL_ERROR;
+ assert(have_primary_data_);
+ assert(have_index_data_ || !uses_index_data_);
+ assert(have_secondary_data_ || !uses_secondary_data_);
+ GLuint gl_draw_type;
+ switch (draw_type) {
+ case DrawType::kTriangles:
+ gl_draw_type = GL_TRIANGLES;
+ break;
+ case DrawType::kPoints:
+ gl_draw_type = GL_POINTS;
+ break;
+ default:
+ throw Exception();
+ }
+ if (uses_index_data_) {
+ glDrawElements(gl_draw_type, elem_count_, index_type_, nullptr);
+ } else {
+ glDrawArrays(gl_draw_type, 0, elem_count_);
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+
+ ~MeshDataGL() override {
+ assert(g_base->InGraphicsThread());
+ // Unbind if we're bound; otherwise we might prevent a new vao that
+ // reuses our ID from binding.
+ if (vao_ == renderer_->current_vertex_array_) {
+ renderer_->BindVertexArray_(0);
+ }
+ if (!g_base->graphics_server->renderer_context_lost()) {
+ glDeleteVertexArrays(1, &vao_);
+ }
+
+ // Make sure our dying buffer isn't current (don't wanna prevent binding
+ // to a new buffer with a recycled id).
+ for (int i = 0; i < GetBufferCount(); i++) {
+ if (vbos_[i] == renderer_->active_array_buffer_) {
+ renderer_->active_array_buffer_ = -1;
+ }
+ }
+ if (!g_base->graphics_server->renderer_context_lost()) {
+ glDeleteBuffers(GetBufferCount(), vbos_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+ }
+
+ void SetIndexData(MeshIndexBuffer32* data) {
+ assert(uses_index_data_);
+ if (data->state != index_state_) {
+ renderer_->BindVertexArray_(vao_);
+ elem_count_ = static_cast(data->elements.size());
+ assert(elem_count_ > 0);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+ static_cast_check_fit(
+ data->elements.size() * sizeof(data->elements[0])),
+ &data->elements[0],
+ dynamic_draw_ ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
+ index_state_ = data->state;
+ have_index_data_ = true;
+ BA_LOG_ONCE(LogLevel::kWarning,
+ "GL WARNING - USING 32 BIT INDICES WHICH WONT WORK IN ES2!!");
+ index_type_ = GL_UNSIGNED_INT;
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+
+ void SetIndexData(MeshIndexBuffer16* data) {
+ assert(uses_index_data_);
+ if (data->state != index_state_) {
+ renderer_->BindVertexArray_(vao_);
+ elem_count_ = static_cast(data->elements.size());
+ assert(elem_count_ > 0);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER,
+ static_cast_check_fit(
+ data->elements.size() * sizeof(data->elements[0])),
+ &data->elements[0],
+ dynamic_draw_ ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
+ index_state_ = data->state;
+ have_index_data_ = true;
+ index_type_ = GL_UNSIGNED_SHORT;
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+
+ // When dynamic-draw is on, it means *all* buffers should be flagged as
+ // dynamic.
+ void set_dynamic_draw(bool enable) { dynamic_draw_ = enable; }
+
+ auto vao() const -> GLuint { return vao_; }
+
+ protected:
+ template
+ void UpdateBufferData(BufferType buffer_type, MeshBuffer* data,
+ uint32_t* state, bool* have, GLuint draw_type) {
+ assert(state && have);
+ if (data->state != *state) {
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ // Hmmm didnt think we had to have vao bound here but causes problems
+ // on qualcomm if not.
+ // #if BA_OSTYPE_ANDROID
+ // if (g_vao_support && renderer_->is_adreno_) {
+ // renderer_->BindVertexArray(vao_);
+ // }
+ // #endif
+ renderer_->BindArrayBuffer(vbos_[buffer_type]);
+ assert(!data->elements.empty());
+ if (!uses_index_data_ && buffer_type == kVertexBufferPrimary) {
+ elem_count_ = static_cast(data->elements.size());
+ }
+ glBufferData(GL_ARRAY_BUFFER,
+ static_cast(data->elements.size()
+ * sizeof(data->elements[0])),
+ &(data->elements[0]), draw_type);
+ BA_DEBUG_CHECK_GL_ERROR;
+ *state = data->state;
+ *have = true;
+ } else {
+ assert(*have);
+ }
+ }
+
+ // FIXME: Should do some sort of ring-buffer system.
+ GLuint vbos_[3]{};
+ GLuint vao_{};
+ auto GetBufferCount() const -> int {
+ return uses_secondary_data_ ? 3 : (uses_index_data_ ? 2 : 1);
+ }
+ uint32_t index_state_{};
+ uint32_t primary_state_{};
+ uint32_t secondary_state_{};
+ bool uses_index_data_{};
+ bool uses_secondary_data_{};
+ bool dynamic_draw_{};
+ bool have_index_data_{};
+ bool have_primary_data_{};
+ bool have_secondary_data_{};
+ RendererGL* renderer_{};
+ uint32_t elem_count_{};
+ GLuint index_type_{GL_UNSIGNED_SHORT};
+ FakeVertexArrayObject* fake_vao_{};
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_GL_H_
diff --git a/src/ballistica/base/graphics/gl/mesh/mesh_data_object_split_gl.h b/src/ballistica/base/graphics/gl/mesh/mesh_data_object_split_gl.h
new file mode 100644
index 00000000..4647a45b
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/mesh/mesh_data_object_split_gl.h
@@ -0,0 +1,53 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_OBJECT_SPLIT_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_OBJECT_SPLIT_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/mesh/mesh_data_gl.h"
+
+namespace ballistica::base {
+
+class RendererGL::MeshDataObjectSplitGL : public RendererGL::MeshDataGL {
+ public:
+ explicit MeshDataObjectSplitGL(RendererGL* renderer)
+ : MeshDataGL(renderer, kUsesSecondaryBuffer | kUsesIndexBuffer) {
+ // Set up our static vertex data.
+ renderer_->BindArrayBuffer(vbos_[kVertexBufferPrimary]);
+ glVertexAttribPointer(
+ kVertexAttrUV, 2, GL_UNSIGNED_SHORT, GL_TRUE,
+ sizeof(VertexObjectSplitStatic),
+ reinterpret_cast(offsetof(VertexObjectSplitStatic, uv)));
+ glEnableVertexAttribArray(kVertexAttrUV);
+
+ // ..and our dynamic vertex data.
+ renderer_->BindArrayBuffer(vbos_[kVertexBufferSecondary]);
+ glVertexAttribPointer(
+ kVertexAttrPosition, 3, GL_FLOAT, GL_FALSE,
+ sizeof(VertexObjectSplitDynamic),
+ reinterpret_cast(offsetof(VertexObjectSplitDynamic, position)));
+ glEnableVertexAttribArray(kVertexAttrPosition);
+ glVertexAttribPointer(
+ kVertexAttrNormal, 3, GL_SHORT, GL_TRUE,
+ sizeof(VertexObjectSplitDynamic),
+ reinterpret_cast(offsetof(VertexObjectSplitDynamic, normal)));
+ glEnableVertexAttribArray(kVertexAttrNormal);
+ }
+ void SetStaticData(MeshBuffer* data) {
+ UpdateBufferData(kVertexBufferPrimary, data, &primary_state_,
+ &have_primary_data_, GL_STATIC_DRAW);
+ }
+ void SetDynamicData(MeshBuffer* data) {
+ assert(uses_secondary_data_);
+ UpdateBufferData(kVertexBufferSecondary, data, &secondary_state_,
+ &have_secondary_data_,
+ GL_DYNAMIC_DRAW); // this is *always* dynamic
+ }
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_OBJECT_SPLIT_GL_H_
diff --git a/src/ballistica/base/graphics/gl/mesh/mesh_data_simple_full_gl.h b/src/ballistica/base/graphics/gl/mesh/mesh_data_simple_full_gl.h
new file mode 100644
index 00000000..f0933cee
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/mesh/mesh_data_simple_full_gl.h
@@ -0,0 +1,39 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_SIMPLE_FULL_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_SIMPLE_FULL_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/mesh/mesh_data_gl.h"
+
+namespace ballistica::base {
+
+class RendererGL::MeshDataSimpleFullGL : public RendererGL::MeshDataGL {
+ public:
+ explicit MeshDataSimpleFullGL(RendererGL* renderer)
+ : MeshDataGL(renderer, kUsesIndexBuffer) {
+ // Set up our vertex data.
+ renderer_->BindArrayBuffer(vbos_[kVertexBufferPrimary]);
+ glVertexAttribPointer(
+ kVertexAttrUV, 2, GL_UNSIGNED_SHORT, GL_TRUE, sizeof(VertexSimpleFull),
+ reinterpret_cast(offsetof(VertexSimpleFull, uv)));
+ glEnableVertexAttribArray(kVertexAttrUV);
+ glVertexAttribPointer(
+ kVertexAttrPosition, 3, GL_FLOAT, GL_FALSE, sizeof(VertexSimpleFull),
+ reinterpret_cast(offsetof(VertexSimpleFull, position)));
+ glEnableVertexAttribArray(kVertexAttrPosition);
+ }
+
+ void SetData(MeshBuffer* data) {
+ UpdateBufferData(kVertexBufferPrimary, data, &primary_state_,
+ &have_primary_data_,
+ dynamic_draw_ ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
+ }
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_SIMPLE_FULL_GL_H_
diff --git a/src/ballistica/base/graphics/gl/mesh/mesh_data_simple_split_gl.h b/src/ballistica/base/graphics/gl/mesh/mesh_data_simple_split_gl.h
new file mode 100644
index 00000000..c2fb57c2
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/mesh/mesh_data_simple_split_gl.h
@@ -0,0 +1,48 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_SIMPLE_SPLIT_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_SIMPLE_SPLIT_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/mesh/mesh_data_gl.h"
+
+namespace ballistica::base {
+
+class RendererGL::MeshDataSimpleSplitGL : public RendererGL::MeshDataGL {
+ public:
+ explicit MeshDataSimpleSplitGL(RendererGL* renderer)
+ : MeshDataGL(renderer, kUsesSecondaryBuffer | kUsesIndexBuffer) {
+ // Set up our static vertex data.
+ renderer_->BindArrayBuffer(vbos_[kVertexBufferPrimary]);
+ glVertexAttribPointer(
+ kVertexAttrUV, 2, GL_UNSIGNED_SHORT, GL_TRUE,
+ sizeof(VertexSimpleSplitStatic),
+ reinterpret_cast(offsetof(VertexSimpleSplitStatic, uv)));
+ glEnableVertexAttribArray(kVertexAttrUV);
+
+ // ..and our dynamic vertex data.
+ renderer_->BindArrayBuffer(vbos_[kVertexBufferSecondary]);
+ glVertexAttribPointer(
+ kVertexAttrPosition, 3, GL_FLOAT, GL_FALSE,
+ sizeof(VertexSimpleSplitDynamic),
+ reinterpret_cast(offsetof(VertexSimpleSplitDynamic, position)));
+ glEnableVertexAttribArray(kVertexAttrPosition);
+ }
+ void SetStaticData(MeshBuffer* data) {
+ UpdateBufferData(kVertexBufferPrimary, data, &primary_state_,
+ &have_primary_data_, GL_STATIC_DRAW);
+ }
+ void SetDynamicData(MeshBuffer* data) {
+ assert(uses_secondary_data_);
+ UpdateBufferData(kVertexBufferSecondary, data, &secondary_state_,
+ &have_secondary_data_,
+ GL_DYNAMIC_DRAW); // this is *always* dynamic
+ }
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_SIMPLE_SPLIT_GL_H_
diff --git a/src/ballistica/base/graphics/gl/mesh/mesh_data_smoke_full_gl.h b/src/ballistica/base/graphics/gl/mesh/mesh_data_smoke_full_gl.h
new file mode 100644
index 00000000..a849f538
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/mesh/mesh_data_smoke_full_gl.h
@@ -0,0 +1,51 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_SMOKE_FULL_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_SMOKE_FULL_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/mesh/mesh_data_gl.h"
+
+namespace ballistica::base {
+
+class RendererGL::MeshDataSmokeFullGL : public RendererGL::MeshDataGL {
+ public:
+ explicit MeshDataSmokeFullGL(RendererGL* renderer)
+ : MeshDataGL(renderer, kUsesIndexBuffer) {
+ // Set up our vertex data.
+ renderer_->BindArrayBuffer(vbos_[kVertexBufferPrimary]);
+ glVertexAttribPointer(
+ kVertexAttrUV, 2, GL_FLOAT, GL_FALSE, sizeof(VertexSmokeFull),
+ reinterpret_cast(offsetof(VertexSmokeFull, uv)));
+ glEnableVertexAttribArray(kVertexAttrUV);
+ glVertexAttribPointer(
+ kVertexAttrPosition, 3, GL_FLOAT, GL_FALSE, sizeof(VertexSmokeFull),
+ reinterpret_cast(offsetof(VertexSmokeFull, position)));
+ glEnableVertexAttribArray(kVertexAttrPosition);
+ glVertexAttribPointer(
+ kVertexAttrErode, 1, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(VertexSmokeFull),
+ reinterpret_cast(offsetof(VertexSmokeFull, erode)));
+ glEnableVertexAttribArray(kVertexAttrErode);
+ glVertexAttribPointer(
+ kVertexAttrDiffuse, 1, GL_UNSIGNED_BYTE, GL_TRUE,
+ sizeof(VertexSmokeFull),
+ reinterpret_cast(offsetof(VertexSmokeFull, diffuse)));
+ glEnableVertexAttribArray(kVertexAttrDiffuse);
+ glVertexAttribPointer(
+ kVertexAttrColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(VertexSmokeFull),
+ reinterpret_cast(offsetof(VertexSmokeFull, color)));
+ glEnableVertexAttribArray(kVertexAttrColor);
+ }
+ void SetData(MeshBuffer* data) {
+ UpdateBufferData(kVertexBufferPrimary, data, &primary_state_,
+ &have_primary_data_,
+ dynamic_draw_ ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
+ }
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_SMOKE_FULL_GL_H_
diff --git a/src/ballistica/base/graphics/gl/mesh/mesh_data_sprite_gl.h b/src/ballistica/base/graphics/gl/mesh/mesh_data_sprite_gl.h
new file mode 100644
index 00000000..fa9e60a2
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/mesh/mesh_data_sprite_gl.h
@@ -0,0 +1,46 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_SPRITE_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_SPRITE_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/mesh/mesh_data_gl.h"
+
+namespace ballistica::base {
+
+class RendererGL::MeshDataSpriteGL : public RendererGL::MeshDataGL {
+ public:
+ explicit MeshDataSpriteGL(RendererGL* renderer)
+ : MeshDataGL(renderer, kUsesIndexBuffer) {
+ // Set up our vertex data.
+ renderer_->BindArrayBuffer(vbos_[kVertexBufferPrimary]);
+ glVertexAttribPointer(
+ kVertexAttrPosition, 3, GL_FLOAT, GL_FALSE, sizeof(VertexSprite),
+ reinterpret_cast(offsetof(VertexSprite, position)));
+ glEnableVertexAttribArray(kVertexAttrPosition);
+ glVertexAttribPointer(kVertexAttrUV, 2, GL_UNSIGNED_SHORT, GL_TRUE,
+ sizeof(VertexSprite),
+ reinterpret_cast(offsetof(VertexSprite, uv)));
+ glEnableVertexAttribArray(kVertexAttrUV);
+ glVertexAttribPointer(
+ kVertexAttrSize, 1, GL_FLOAT, GL_FALSE, sizeof(VertexSprite),
+ reinterpret_cast(offsetof(VertexSprite, size)));
+ glEnableVertexAttribArray(kVertexAttrSize);
+ glVertexAttribPointer(
+ kVertexAttrColor, 4, GL_FLOAT, GL_FALSE, sizeof(VertexSprite),
+ reinterpret_cast(offsetof(VertexSprite, color)));
+ glEnableVertexAttribArray(kVertexAttrColor);
+ }
+ void SetData(MeshBuffer* data) {
+ UpdateBufferData(kVertexBufferPrimary, data, &primary_state_,
+ &have_primary_data_,
+ dynamic_draw_ ? GL_DYNAMIC_DRAW : GL_STATIC_DRAW);
+ }
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_MESH_MESH_DATA_SPRITE_GL_H_
diff --git a/src/ballistica/base/graphics/gl/program/program_blur_gl.h b/src/ballistica/base/graphics/gl/program/program_blur_gl.h
new file mode 100644
index 00000000..ddd0219a
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/program/program_blur_gl.h
@@ -0,0 +1,138 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_BLUR_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_BLUR_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/program/program_gl.h"
+#include "ballistica/base/graphics/gl/renderer_gl.h"
+#include "ballistica/base/graphics/graphics_server.h"
+
+namespace ballistica::base {
+
+class RendererGL::ProgramBlurGL : public RendererGL::ProgramGL {
+ public:
+ enum TextureUnit {
+ kColorTexUnit,
+ };
+
+ ProgramBlurGL(RendererGL* renderer, int flags)
+ : RendererGL::ProgramGL(
+ renderer, Object::New(GetVertexCode(flags)),
+ Object::New(GetFragmentCode(flags)), GetName(flags),
+ GetPFlags(flags)),
+ flags_(flags),
+ pixel_size_x_(0.0f),
+ pixel_size_y_(0.0f) {
+ SetTextureUnit("colorTex", kColorTexUnit);
+ pixel_size_location_ = glGetUniformLocation(program(), "pixelSize");
+ assert(pixel_size_location_ != -1);
+ }
+
+ void SetPixelSize(float x, float y) {
+ assert(IsBound());
+ if (x != pixel_size_x_ || y != pixel_size_y_) {
+ pixel_size_x_ = x;
+ pixel_size_y_ = y;
+ glUniform2f(pixel_size_location_, pixel_size_x_, pixel_size_y_);
+ }
+ }
+
+ void SetColorTexture(const TextureAsset* t) {
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kColorTexUnit);
+ }
+
+ void SetColorTexture(GLuint t) {
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kColorTexUnit);
+ }
+
+ private:
+ auto GetName(int flags) -> std::string {
+ return std::string("BlurProgramGL");
+ }
+
+ auto GetPFlags(int flags) -> int {
+ int pflags = PFLAG_USES_POSITION_ATTR | PFLAG_USES_UV_ATTR;
+ return pflags;
+ }
+
+ auto GetVertexCode(int flags) -> std::string {
+ std::string s;
+ s = "uniform mat4 modelViewProjectionMatrix;\n" BA_GLSL_VERTEX_IN
+ " vec4 position;\n" BA_GLSL_VERTEX_IN " " BA_GLSL_MEDIUMP
+ "vec2 uv;\n" BA_GLSL_VERTEX_OUT " " BA_GLSL_MEDIUMP
+ "vec2 vUV1;\n" BA_GLSL_VERTEX_OUT " " BA_GLSL_MEDIUMP
+ "vec2 vUV2;\n" BA_GLSL_VERTEX_OUT " " BA_GLSL_MEDIUMP
+ "vec2 vUV3;\n" BA_GLSL_VERTEX_OUT " " BA_GLSL_MEDIUMP
+ "vec2 vUV4;\n" BA_GLSL_VERTEX_OUT " " BA_GLSL_MEDIUMP
+ "vec2 vUV5;\n" BA_GLSL_VERTEX_OUT " " BA_GLSL_MEDIUMP
+ "vec2 vUV6;\n" BA_GLSL_VERTEX_OUT " " BA_GLSL_MEDIUMP
+ "vec2 vUV7;\n" BA_GLSL_VERTEX_OUT " " BA_GLSL_MEDIUMP
+ "vec2 vUV8;\n"
+ "uniform " BA_GLSL_MEDIUMP
+ "vec2 pixelSize;\n"
+ "void main() {\n"
+ " gl_Position = modelViewProjectionMatrix*position;\n"
+ " vUV1 = uv+vec2(-0.5,0)*pixelSize;\n"
+ " vUV2 = uv+vec2(-1.5,0)*pixelSize;\n"
+ " vUV3 = uv+vec2(0.5,0)*pixelSize;\n"
+ " vUV4 = uv+vec2(1.5,0)*pixelSize;\n"
+ " vUV5 = uv+vec2(-0.5,1.0)*pixelSize;\n"
+ " vUV6 = uv+vec2(0.5,1.0)*pixelSize;\n"
+ " vUV7 = uv+vec2(-0.5,-1.0)*pixelSize;\n"
+ " vUV8 = uv+vec2(0.5,-1.0)*pixelSize;\n";
+ s += "}";
+
+ if (flags & SHD_DEBUG_PRINT)
+ Log(LogLevel::kInfo,
+ "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s);
+ return s;
+ }
+
+ auto GetFragmentCode(int flags) -> std::string {
+ std::string s;
+ s = "uniform " BA_GLSL_MEDIUMP "sampler2D colorTex;\n" BA_GLSL_FRAG_IN
+ " " BA_GLSL_MEDIUMP "vec2 vUV1;\n" BA_GLSL_FRAG_IN " " BA_GLSL_MEDIUMP
+ "vec2 vUV2;\n" BA_GLSL_FRAG_IN " " BA_GLSL_MEDIUMP
+ "vec2 vUV3;\n" BA_GLSL_FRAG_IN " " BA_GLSL_MEDIUMP
+ "vec2 vUV4;\n" BA_GLSL_FRAG_IN " " BA_GLSL_MEDIUMP
+ "vec2 vUV5;\n" BA_GLSL_FRAG_IN " " BA_GLSL_MEDIUMP
+ "vec2 vUV6;\n" BA_GLSL_FRAG_IN " " BA_GLSL_MEDIUMP
+ "vec2 vUV7;\n" BA_GLSL_FRAG_IN " " BA_GLSL_MEDIUMP
+ "vec2 vUV8;\n"
+ "void main() {\n"
+ " " BA_GLSL_FRAGCOLOR " = 0.125*(" BA_GLSL_TEXTURE2D
+ "(colorTex,vUV1)\n"
+ " + " BA_GLSL_TEXTURE2D
+ "(colorTex,vUV2)\n"
+ " + " BA_GLSL_TEXTURE2D
+ "(colorTex,vUV3)\n"
+ " + " BA_GLSL_TEXTURE2D
+ "(colorTex,vUV4)\n"
+ " + " BA_GLSL_TEXTURE2D
+ "(colorTex,vUV5)\n"
+ " + " BA_GLSL_TEXTURE2D
+ "(colorTex,vUV6)\n"
+ " + " BA_GLSL_TEXTURE2D
+ "(colorTex,vUV7)\n"
+ " + " BA_GLSL_TEXTURE2D
+ "(colorTex,vUV8));\n"
+ "}";
+ if (flags & SHD_DEBUG_PRINT) {
+ Log(LogLevel::kInfo,
+ "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s);
+ }
+ return s;
+ }
+
+ int flags_;
+ GLint pixel_size_location_;
+ float pixel_size_x_, pixel_size_y_;
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_BLUR_GL_H_
diff --git a/src/ballistica/base/graphics/gl/program/program_gl.h b/src/ballistica/base/graphics/gl/program/program_gl.h
new file mode 100644
index 00000000..e7fea6bc
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/program/program_gl.h
@@ -0,0 +1,358 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/renderer_gl.h"
+#include "ballistica/base/graphics/graphics_server.h"
+
+namespace ballistica::base {
+
+// Base class for fragment/vertex shaders.
+class RendererGL::ShaderGL : public Object {
+ public:
+ auto GetDefaultOwnerThread() const -> EventLoopID override {
+ return EventLoopID::kMain;
+ }
+
+ ShaderGL(GLenum type_in, const std::string& src_in) : type_(type_in) {
+ assert(g_base->InGraphicsThread());
+ BA_DEBUG_CHECK_GL_ERROR;
+ assert(type_ == GL_FRAGMENT_SHADER || type_ == GL_VERTEX_SHADER);
+ shader_ = glCreateShader(type_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ BA_PRECONDITION(shader_);
+#if !BA_OPENGL_IS_ES
+ std::string src_fin = src_in;
+ if (type_ == GL_FRAGMENT_SHADER) {
+ // gl_FragColor is no more. Define our equivalent.
+ src_fin = "out vec4 " BA_GLSL_FRAGCOLOR ";\n" + src_fin;
+ }
+ src_fin = "#version 150 core\n" + src_fin;
+#else
+ std::string src_fin = src_in;
+#endif
+ const char* s = src_fin.c_str();
+ glShaderSource(shader_, 1, &s, nullptr);
+ glCompileShader(shader_);
+ GLint compile_status;
+ glGetShaderiv(shader_, GL_COMPILE_STATUS, &compile_status);
+ if (compile_status == GL_FALSE) {
+ const char* version = (const char*)glGetString(GL_VERSION);
+ const char* vendor = (const char*)glGetString(GL_VENDOR);
+ const char* renderer = (const char*)glGetString(GL_RENDERER);
+ // Let's not crash here. We have a better chance of calling home this
+ // way and theres a chance the game will still be playable.
+ Log(LogLevel::kError,
+ std::string("Compile failed for ") + GetTypeName()
+ + " shader:\n------------SOURCE BEGIN-------------\n" + src_fin
+ + "\n-----------SOURCE END-------------\n" + GetInfo()
+ + "\nrenderer: " + renderer + "\nvendor: " + vendor
+ + "\nversion:" + version);
+ } else {
+ assert(compile_status == GL_TRUE);
+ std::string info = GetInfo();
+ if (!info.empty()
+ && (strstr(info.c_str(), "error:") || strstr(info.c_str(), "warning:")
+ || strstr(info.c_str(), "Error:")
+ || strstr(info.c_str(), "Warning:"))) {
+ const char* version = (const char*)glGetString(GL_VERSION);
+ const char* vendor = (const char*)glGetString(GL_VENDOR);
+ const char* renderer = (const char*)glGetString(GL_RENDERER);
+ Log(LogLevel::kError,
+ std::string("WARNING: info returned for ") + GetTypeName()
+ + " shader:\n------------SOURCE BEGIN-------------\n" + src_fin
+ + "\n-----------SOURCE END-------------\n" + info
+ + "\nrenderer: " + renderer + "\nvendor: " + vendor
+ + "\nversion:" + version);
+ }
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+
+ ~ShaderGL() override {
+ assert(g_base->InGraphicsThread());
+ if (!g_base->graphics_server->renderer_context_lost()) {
+ glDeleteShader(shader_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+ }
+
+ auto shader() const -> GLuint { return shader_; }
+
+ private:
+ auto GetTypeName() const -> const char* {
+ if (type_ == GL_VERTEX_SHADER) {
+ return "vertex";
+ } else {
+ return "fragment";
+ }
+ }
+
+ auto GetInfo() -> std::string {
+ static char log[1024];
+ GLsizei log_size;
+ glGetShaderInfoLog(shader_, sizeof(log), &log_size, log);
+ return log;
+ }
+
+ std::string name_;
+ GLuint shader_{};
+ GLenum type_{};
+ BA_DISALLOW_CLASS_COPIES(ShaderGL);
+};
+
+class RendererGL::FragmentShaderGL : public RendererGL::ShaderGL {
+ public:
+ explicit FragmentShaderGL(const std::string& src_in)
+ : ShaderGL(GL_FRAGMENT_SHADER, src_in) {}
+};
+
+class RendererGL::VertexShaderGL : public RendererGL::ShaderGL {
+ public:
+ explicit VertexShaderGL(const std::string& src_in)
+ : ShaderGL(GL_VERTEX_SHADER, src_in) {}
+};
+
+class RendererGL::ProgramGL {
+ public:
+ ProgramGL(RendererGL* renderer,
+ const Object::Ref& vertex_shader_in,
+ const Object::Ref& fragment_shader_in,
+ std::string name, int pflags)
+ : fragment_shader_(fragment_shader_in),
+ vertex_shader_(vertex_shader_in),
+ renderer_(renderer),
+ pflags_(pflags),
+ name_(std::move(name)) {
+ assert(g_base->InGraphicsThread());
+ BA_DEBUG_CHECK_GL_ERROR;
+ program_ = glCreateProgram();
+ BA_PRECONDITION(program_);
+ glAttachShader(program_, fragment_shader_->shader());
+ glAttachShader(program_, vertex_shader_->shader());
+ assert(pflags_ & PFLAG_USES_POSITION_ATTR);
+ if (pflags_ & PFLAG_USES_POSITION_ATTR) {
+ glBindAttribLocation(program_, kVertexAttrPosition, "position");
+ }
+ if (pflags_ & PFLAG_USES_UV_ATTR) {
+ glBindAttribLocation(program_, kVertexAttrUV, "uv");
+ }
+ if (pflags_ & PFLAG_USES_NORMAL_ATTR) {
+ glBindAttribLocation(program_, kVertexAttrNormal, "normal");
+ }
+ if (pflags_ & PFLAG_USES_ERODE_ATTR) {
+ glBindAttribLocation(program_, kVertexAttrErode, "erode");
+ }
+ if (pflags_ & PFLAG_USES_COLOR_ATTR) {
+ glBindAttribLocation(program_, kVertexAttrColor, "color");
+ }
+ if (pflags_ & PFLAG_USES_SIZE_ATTR) {
+ glBindAttribLocation(program_, kVertexAttrSize, "size");
+ }
+ if (pflags_ & PFLAG_USES_DIFFUSE_ATTR) {
+ glBindAttribLocation(program_, kVertexAttrDiffuse, "diffuse");
+ }
+ if (pflags_ & PFLAG_USES_UV2_ATTR) {
+ glBindAttribLocation(program_, kVertexAttrUV2, "uv2");
+ }
+ glLinkProgram(program_);
+ GLint linkStatus;
+ glGetProgramiv(program_, GL_LINK_STATUS, &linkStatus);
+ if (linkStatus == GL_FALSE) {
+ Log(LogLevel::kError,
+ "Link failed for program '" + name_ + "':\n" + GetInfo());
+ } else {
+ assert(linkStatus == GL_TRUE);
+
+ std::string info = GetInfo();
+ if (!info.empty()
+ && (strstr(info.c_str(), "error:") || strstr(info.c_str(), "warning:")
+ || strstr(info.c_str(), "Error:")
+ || strstr(info.c_str(), "Warning:"))) {
+ Log(LogLevel::kError, "WARNING: program using frag shader '" + name_
+ + "' returned info:\n" + info);
+ }
+ }
+
+ // Go ahead and bind ourself so child classes can config uniforms and
+ // whatnot.
+ Bind();
+ mvp_uniform_ = glGetUniformLocation(program_, "modelViewProjectionMatrix");
+ assert(mvp_uniform_ != -1);
+ if (pflags_ & PFLAG_USES_MODEL_WORLD_MATRIX) {
+ model_world_matrix_uniform_ =
+ glGetUniformLocation(program_, "modelWorldMatrix");
+ assert(model_world_matrix_uniform_ != -1);
+ }
+ if (pflags_ & PFLAG_USES_MODEL_VIEW_MATRIX) {
+ model_view_matrix_uniform_ =
+ glGetUniformLocation(program_, "modelViewMatrix");
+ assert(model_view_matrix_uniform_ != -1);
+ }
+ if (pflags_ & PFLAG_USES_CAM_POS) {
+ cam_pos_uniform_ = glGetUniformLocation(program_, "camPos");
+ assert(cam_pos_uniform_ != -1);
+ }
+ if (pflags_ & PFLAG_USES_CAM_ORIENT_MATRIX) {
+ cam_orient_matrix_uniform_ =
+ glGetUniformLocation(program_, "camOrientMatrix");
+ assert(cam_orient_matrix_uniform_ != -1);
+ }
+ if (pflags_ & PFLAG_USES_SHADOW_PROJECTION_MATRIX) {
+ light_shadow_projection_matrix_uniform_ =
+ glGetUniformLocation(program_, "lightShadowProjectionMatrix");
+ assert(light_shadow_projection_matrix_uniform_ != -1);
+ }
+ }
+
+ virtual ~ProgramGL() {
+ assert(g_base->InGraphicsThread());
+ if (!g_base->graphics_server->renderer_context_lost()) {
+ glDetachShader(program_, fragment_shader_->shader());
+ glDetachShader(program_, vertex_shader_->shader());
+ glDeleteProgram(program_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+ }
+
+ auto IsBound() const -> bool {
+ return (renderer()->GetActiveProgram_() == this);
+ }
+
+ auto program() const -> GLuint { return program_; }
+
+ void Bind() { renderer_->UseProgram_(this); }
+
+ auto name() const -> const std::string& { return name_; }
+
+ // Should grab matrices from the renderer or whatever else it needs in
+ // prep for drawing.
+ void PrepareToDraw() {
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ assert(IsBound());
+
+ // Update matrices as necessary.
+
+ uint32_t mvpState =
+ g_base->graphics_server->GetModelViewProjectionMatrixState();
+ if (mvpState != mvp_state_) {
+ mvp_state_ = mvpState;
+ glUniformMatrix4fv(
+ mvp_uniform_, 1, 0,
+ g_base->graphics_server->GetModelViewProjectionMatrix().m);
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ if (pflags_ & PFLAG_USES_MODEL_WORLD_MATRIX) {
+ // With world space points this would be identity; don't waste time.
+ assert(!(pflags_ & PFLAG_WORLD_SPACE_PTS));
+ uint32_t state = g_base->graphics_server->GetModelWorldMatrixState();
+ if (state != model_world_matrix_state_) {
+ model_world_matrix_state_ = state;
+ glUniformMatrix4fv(model_world_matrix_uniform_, 1, 0,
+ g_base->graphics_server->GetModelWorldMatrix().m);
+ }
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ if (pflags_ & PFLAG_USES_MODEL_VIEW_MATRIX) {
+ // With world space points this would be identity; don't waste time.
+ assert(!(pflags_ & PFLAG_WORLD_SPACE_PTS));
+ // There's no state for just modelview but this works.
+ uint32_t state =
+ g_base->graphics_server->GetModelViewProjectionMatrixState();
+ if (state != model_view_matrix_state_) {
+ model_view_matrix_state_ = state;
+ glUniformMatrix4fv(model_view_matrix_uniform_, 1, 0,
+ g_base->graphics_server->model_view_matrix().m);
+ }
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ if (pflags_ & PFLAG_USES_CAM_POS) {
+ uint32_t state = g_base->graphics_server->cam_pos_state();
+ if (state != cam_pos_state_) {
+ cam_pos_state_ = state;
+ const Vector3f& p(g_base->graphics_server->cam_pos());
+ glUniform4f(cam_pos_uniform_, p.x, p.y, p.z, 1.0f);
+ }
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ if (pflags_ & PFLAG_USES_CAM_ORIENT_MATRIX) {
+ uint32_t state = g_base->graphics_server->GetCamOrientMatrixState();
+ if (state != cam_orient_matrix_state_) {
+ cam_orient_matrix_state_ = state;
+ glUniformMatrix4fv(cam_orient_matrix_uniform_, 1, 0,
+ g_base->graphics_server->GetCamOrientMatrix().m);
+ }
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ if (pflags_ & PFLAG_USES_SHADOW_PROJECTION_MATRIX) {
+ uint32_t state =
+ g_base->graphics_server->light_shadow_projection_matrix_state();
+ if (state != light_shadow_projection_matrix_state_) {
+ light_shadow_projection_matrix_state_ = state;
+ glUniformMatrix4fv(
+ light_shadow_projection_matrix_uniform_, 1, 0,
+ g_base->graphics_server->light_shadow_projection_matrix().m);
+ }
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+
+ protected:
+ void SetTextureUnit(const char* tex_name, int unit) {
+ assert(IsBound());
+ int c = glGetUniformLocation(program_, tex_name);
+ if (c == -1) {
+ Log(LogLevel::kError, "ShaderGL: " + name_
+ + ": Can't set texture unit for texture '"
+ + tex_name + "'");
+ BA_DEBUG_CHECK_GL_ERROR;
+ } else {
+ glUniform1i(c, unit);
+ }
+ }
+
+ auto GetInfo() -> std::string {
+ static char log[1024];
+ GLsizei log_size;
+ glGetProgramInfoLog(program_, sizeof(log), &log_size, log);
+ return log;
+ }
+
+ auto renderer() const -> RendererGL* { return renderer_; }
+
+ private:
+ RendererGL* renderer_{};
+ Object::Ref fragment_shader_;
+ Object::Ref vertex_shader_;
+ std::string name_;
+ GLuint program_{};
+ int pflags_{};
+ uint32_t mvp_state_{};
+ GLint mvp_uniform_{};
+ GLint model_world_matrix_uniform_{};
+ GLint model_view_matrix_uniform_{};
+ GLint light_shadow_projection_matrix_uniform_{};
+ uint32_t light_shadow_projection_matrix_state_{};
+ uint32_t model_world_matrix_state_{};
+ uint32_t model_view_matrix_state_{};
+ GLint cam_pos_uniform_{};
+ uint32_t cam_pos_state_{};
+ GLint cam_orient_matrix_uniform_{};
+ GLuint cam_orient_matrix_state_{};
+ BA_DISALLOW_CLASS_COPIES(ProgramGL);
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_GL_H_
diff --git a/src/ballistica/base/graphics/gl/program/program_object_gl.h b/src/ballistica/base/graphics/gl/program/program_object_gl.h
new file mode 100644
index 00000000..0941ea9f
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/program/program_object_gl.h
@@ -0,0 +1,345 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_OBJECT_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_OBJECT_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/program/program_gl.h"
+#include "ballistica/base/graphics/gl/renderer_gl.h"
+#include "ballistica/base/graphics/graphics_server.h"
+
+namespace ballistica::base {
+
+class RendererGL::ProgramObjectGL : public RendererGL::ProgramGL {
+ public:
+ enum TextureUnit {
+ kColorTexUnit,
+ kReflectionTexUnit,
+ kVignetteTexUnit,
+ kLightShadowTexUnit,
+ kColorizeTexUnit
+ };
+
+ ProgramObjectGL(RendererGL* renderer, int flags)
+ : RendererGL::ProgramGL(
+ renderer, Object::New(GetVertexCode(flags)),
+ Object::New(GetFragmentCode(flags)), GetName(flags),
+ GetPFlags(flags)),
+ flags_(flags),
+ r_(0),
+ g_(0),
+ b_(0),
+ a_(0),
+ colorize_r_(0),
+ colorize_g_(0),
+ colorize_b_(0),
+ colorize_a_(0),
+ colorize2_r_(0),
+ colorize2_g_(0),
+ colorize2_b_(0),
+ colorize2_a_(0),
+ add_r_(0),
+ add_g_(0),
+ add_b_(0),
+ r_mult_r_(0),
+ r_mult_g_(0),
+ r_mult_b_(0),
+ r_mult_a_(0) {
+ SetTextureUnit("colorTex", kColorTexUnit);
+ SetTextureUnit("vignetteTex", kVignetteTexUnit);
+ color_location_ = glGetUniformLocation(program(), "color");
+ assert(color_location_ != -1);
+ if (flags & SHD_REFLECTION) {
+ SetTextureUnit("reflectionTex", kReflectionTexUnit);
+ reflect_mult_location_ = glGetUniformLocation(program(), "reflectMult");
+ assert(reflect_mult_location_ != -1);
+ }
+ if (flags & SHD_LIGHT_SHADOW) {
+ SetTextureUnit("lightShadowTex", kLightShadowTexUnit);
+ }
+ if (flags & SHD_ADD) {
+ color_add_location_ = glGetUniformLocation(program(), "colorAdd");
+ assert(color_add_location_ != -1);
+ }
+ if (flags & SHD_COLORIZE) {
+ SetTextureUnit("colorizeTex", kColorizeTexUnit);
+ colorize_color_location_ =
+ glGetUniformLocation(program(), "colorizeColor");
+ assert(colorize_color_location_ != -1);
+ }
+ if (flags & SHD_COLORIZE2) {
+ colorize2_color_location_ =
+ glGetUniformLocation(program(), "colorize2Color");
+ assert(colorize2_color_location_ != -1);
+ }
+ }
+
+ void SetColorTexture(const TextureAsset* t) {
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kColorTexUnit);
+ }
+
+ void SetReflectionTexture(const TextureAsset* t) {
+ assert(flags_ & SHD_REFLECTION);
+ renderer()->BindTexture_(GL_TEXTURE_CUBE_MAP, t, kReflectionTexUnit);
+ }
+
+ void SetColor(float r, float g, float b, float a = 1.0f) {
+ assert(IsBound());
+ // include tint..
+ if (r * renderer()->tint().x != r_ || g * renderer()->tint().y != g_
+ || b * renderer()->tint().z != b_ || a != a_) {
+ r_ = r * renderer()->tint().x;
+ g_ = g * renderer()->tint().y;
+ b_ = b * renderer()->tint().z;
+ a_ = a;
+ glUniform4f(color_location_, r_, g_, b_, a_);
+ }
+ }
+
+ void SetAddColor(float r, float g, float b) {
+ assert(IsBound());
+ if (r != add_r_ || g != add_g_ || b != add_b_) {
+ add_r_ = r;
+ add_g_ = g;
+ add_b_ = b;
+ glUniform4f(color_add_location_, add_r_, add_g_, add_b_, 0.0f);
+ }
+ }
+
+ void SetReflectionMult(float r, float g, float b, float a = 0.0f) {
+ assert(IsBound());
+ // include tint and ambient color...
+ auto renderer = this->renderer();
+ float rFin = r * renderer->tint().x * renderer->ambient_color().x;
+ float gFin = g * renderer->tint().y * renderer->ambient_color().y;
+ float bFin = b * renderer->tint().z * renderer->ambient_color().z;
+ if (rFin != r_mult_r_ || gFin != r_mult_g_ || bFin != r_mult_b_
+ || a != r_mult_a_) {
+ r_mult_r_ = rFin;
+ r_mult_g_ = gFin;
+ r_mult_b_ = bFin;
+ r_mult_a_ = a;
+ assert(flags_ & SHD_REFLECTION);
+ glUniform4f(reflect_mult_location_, r_mult_r_, r_mult_g_, r_mult_b_,
+ r_mult_a_);
+ }
+ }
+
+ void SetVignetteTexture(GLuint t) {
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kVignetteTexUnit);
+ }
+
+ void SetLightShadowTexture(GLuint t) {
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kLightShadowTexUnit);
+ }
+
+ void SetColorizeColor(float r, float g, float b, float a = 1.0f) {
+ assert(flags_ & SHD_COLORIZE);
+ assert(IsBound());
+ if (r != colorize_r_ || g != colorize_g_ || b != colorize_b_
+ || a != colorize_a_) {
+ colorize_r_ = r;
+ colorize_g_ = g;
+ colorize_b_ = b;
+ colorize_a_ = a;
+ glUniform4f(colorize_color_location_, colorize_r_, colorize_g_,
+ colorize_b_, colorize_a_);
+ }
+ }
+
+ void SetColorize2Color(float r, float g, float b, float a = 1.0f) {
+ assert(flags_ & SHD_COLORIZE2);
+ assert(IsBound());
+ if (r != colorize2_r_ || g != colorize2_g_ || b != colorize2_b_
+ || a != colorize2_a_) {
+ colorize2_r_ = r;
+ colorize2_g_ = g;
+ colorize2_b_ = b;
+ colorize2_a_ = a;
+ glUniform4f(colorize2_color_location_, colorize2_r_, colorize2_g_,
+ colorize2_b_, colorize2_a_);
+ }
+ }
+
+ void SetColorizeTexture(const TextureAsset* t) {
+ assert(flags_ & SHD_COLORIZE);
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kColorizeTexUnit);
+ }
+
+ private:
+ auto GetName(int flags) -> std::string {
+ return std::string("ProgramObjectGL")
+ + " reflect:" + std::to_string((flags & SHD_REFLECTION) != 0)
+ + " lightShadow:" + std::to_string((flags & SHD_LIGHT_SHADOW) != 0)
+ + " add:" + std::to_string((flags & SHD_ADD) != 0) + " colorize:"
+ + std::to_string((flags & SHD_COLORIZE) != 0) + " colorize2:"
+ + std::to_string((flags & SHD_COLORIZE2) != 0) + " transparent:"
+ + std::to_string((flags & SHD_OBJ_TRANSPARENT) != 0) + " worldSpace:"
+ + std::to_string((flags & SHD_WORLD_SPACE_PTS) != 0);
+ }
+
+ auto GetPFlags(int flags) -> int {
+ int pflags = PFLAG_USES_POSITION_ATTR | PFLAG_USES_UV_ATTR;
+ if (flags & SHD_REFLECTION)
+ pflags |= (PFLAG_USES_NORMAL_ATTR | PFLAG_USES_CAM_POS);
+ if (((flags & SHD_REFLECTION) || (flags & SHD_LIGHT_SHADOW))
+ && !(flags & SHD_WORLD_SPACE_PTS))
+ pflags |= PFLAG_USES_MODEL_WORLD_MATRIX;
+ if (flags & SHD_LIGHT_SHADOW) pflags |= PFLAG_USES_SHADOW_PROJECTION_MATRIX;
+ if (flags & SHD_WORLD_SPACE_PTS) pflags |= PFLAG_WORLD_SPACE_PTS;
+ return pflags;
+ }
+
+ auto GetVertexCode(int flags) -> std::string {
+ std::string s;
+ s = "uniform mat4 modelViewProjectionMatrix;\n"
+ "uniform vec4 camPos;\n" BA_GLSL_VERTEX_IN
+ " vec4 position;\n" BA_GLSL_VERTEX_IN " " BA_GLSL_LOWP
+ "vec2 uv;\n" BA_GLSL_VERTEX_OUT " " BA_GLSL_LOWP
+ "vec2 vUV;\n" BA_GLSL_VERTEX_OUT " " BA_GLSL_MEDIUMP
+ "vec4 vScreenCoord;\n";
+ if ((flags & SHD_REFLECTION) || (flags & SHD_LIGHT_SHADOW))
+ s += "uniform mat4 modelWorldMatrix;\n";
+ if (flags & SHD_REFLECTION)
+ s += BA_GLSL_VERTEX_IN " " BA_GLSL_MEDIUMP
+ "vec3 normal;\n" BA_GLSL_VERTEX_OUT
+ " " BA_GLSL_MEDIUMP "vec3 vReflect;\n";
+ if (flags & SHD_LIGHT_SHADOW)
+ s += "uniform mat4 lightShadowProjectionMatrix;\n" BA_GLSL_VERTEX_OUT
+ " " BA_GLSL_MEDIUMP "vec4 vLightShadowUV;\n";
+ s +=
+ "void main() {\n"
+ " vUV = uv;\n"
+ " gl_Position = modelViewProjectionMatrix*position;\n"
+ " vScreenCoord = vec4(gl_Position.xy/gl_Position.w,gl_Position.zw);\n"
+ " vScreenCoord.xy += vec2(1.0);\n"
+ " vScreenCoord.xy *= vec2(0.5*vScreenCoord.w);\n";
+ if (((flags & SHD_LIGHT_SHADOW) || (flags & SHD_REFLECTION))
+ && !(flags & SHD_WORLD_SPACE_PTS)) {
+ s += " vec4 worldPos = modelWorldMatrix*position;\n";
+ }
+ if (flags & SHD_LIGHT_SHADOW) {
+ if (flags & SHD_WORLD_SPACE_PTS)
+ s += " vLightShadowUV = (lightShadowProjectionMatrix*position);\n";
+ else
+ s += " vLightShadowUV = (lightShadowProjectionMatrix*worldPos);\n";
+ }
+ if (flags & SHD_REFLECTION) {
+ if (flags & SHD_WORLD_SPACE_PTS)
+ s += " vReflect = reflect(vec3(position - camPos),normal);\n";
+ else
+ s += " vReflect = reflect(vec3(worldPos - "
+ "camPos),normalize(vec3(modelWorldMatrix * vec4(normal,0.0))));\n";
+ }
+ s += "}";
+ if (flags & SHD_DEBUG_PRINT)
+ Log(LogLevel::kInfo,
+ "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s);
+ return s;
+ }
+
+ auto GetFragmentCode(int flags) -> std::string {
+ std::string s;
+ s = "uniform " BA_GLSL_LOWP
+ "sampler2D colorTex;\n"
+ "uniform " BA_GLSL_LOWP
+ "sampler2D vignetteTex;\n"
+ "uniform " BA_GLSL_LOWP "vec4 color;\n" BA_GLSL_FRAG_IN " " BA_GLSL_LOWP
+ "vec2 vUV;\n" BA_GLSL_FRAG_IN " " BA_GLSL_MEDIUMP
+ "vec4 vScreenCoord;\n";
+ if (flags & SHD_ADD) {
+ s += "uniform " BA_GLSL_LOWP "vec4 colorAdd;\n";
+ }
+ if (flags & SHD_REFLECTION) {
+ s += "uniform " BA_GLSL_LOWP
+ "samplerCube reflectionTex;\n" BA_GLSL_FRAG_IN " " BA_GLSL_MEDIUMP
+ "vec3 vReflect;\n"
+ "uniform " BA_GLSL_LOWP "vec4 reflectMult;\n";
+ }
+ if (flags & SHD_COLORIZE) {
+ s += "uniform " BA_GLSL_LOWP
+ "sampler2D colorizeTex;\n"
+ "uniform " BA_GLSL_LOWP "vec4 colorizeColor;\n";
+ }
+ if (flags & SHD_COLORIZE2) {
+ s += "uniform " BA_GLSL_LOWP "vec4 colorize2Color;\n";
+ }
+ if (flags & SHD_LIGHT_SHADOW) {
+ s += "uniform " BA_GLSL_LOWP "sampler2D lightShadowTex;\n" BA_GLSL_FRAG_IN
+ " " BA_GLSL_MEDIUMP "vec4 vLightShadowUV;\n";
+ }
+ s += "void main() {\n";
+ if (flags & SHD_LIGHT_SHADOW) {
+ s += " " BA_GLSL_LOWP "vec4 lightShadVal = " BA_GLSL_TEXTURE2DPROJ
+ "(lightShadowTex, vLightShadowUV);\n";
+ }
+ if ((flags & SHD_COLORIZE) || (flags & SHD_COLORIZE2)) {
+ s += " " BA_GLSL_LOWP "vec4 colorizeVal = " BA_GLSL_TEXTURE2D
+ "(colorizeTex, vUV);\n";
+ }
+ if (flags & SHD_COLORIZE) {
+ s += " " BA_GLSL_LOWP "float colorizeA = colorizeVal.r;\n";
+ }
+ if (flags & SHD_COLORIZE2) {
+ s += " " BA_GLSL_LOWP "float colorizeB = colorizeVal.g;\n";
+ }
+ s += " " BA_GLSL_FRAGCOLOR " = (color * " BA_GLSL_TEXTURE2D
+ "(colorTex, vUV)";
+ if (flags & SHD_COLORIZE) {
+ s += " * (vec4(1.0-colorizeA)+colorizeColor*colorizeA)";
+ }
+ if (flags & SHD_COLORIZE2) {
+ s += " * (vec4(1.0-colorizeB)+colorize2Color*colorizeB)";
+ }
+ s += ")";
+
+ // add in lights/shadows
+ if (flags & SHD_LIGHT_SHADOW) {
+ if (flags & SHD_OBJ_TRANSPARENT) {
+ s += " * vec4((2.0 * lightShadVal).rgb, 1) + "
+ "vec4((lightShadVal - 0.5).rgb,0)";
+ } else {
+ s += " * (2.0 * lightShadVal) + (lightShadVal - 0.5)";
+ }
+ }
+
+ // add glow and reflection
+ if (flags & SHD_REFLECTION)
+ s += " + (reflectMult*" BA_GLSL_TEXTURECUBE "(reflectionTex, vReflect))";
+ if (flags & SHD_ADD) s += " + colorAdd";
+
+ // subtract vignette
+ s += " - vec4(" BA_GLSL_TEXTURE2DPROJ "(vignetteTex, vScreenCoord).rgb,0)";
+
+ s += ";\n";
+ // s += BA_GLSL_FRAGCOLOR " = 0.999 * " BA_GLSL_TEXTURE2DPROJ
+ // "(vignetteTex,vScreenCoord)
+ // + 0.01 * BA_GLSL_FRAGCOLOR ";";
+
+ s += "}";
+
+ if (flags & SHD_DEBUG_PRINT)
+ Log(LogLevel::kInfo,
+ "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s);
+ return s;
+ }
+
+ float r_, g_, b_, a_;
+ float colorize_r_, colorize_g_, colorize_b_, colorize_a_;
+ float colorize2_r_, colorize2_g_, colorize2_b_, colorize2_a_;
+ float add_r_, add_g_, add_b_;
+ float r_mult_r_, r_mult_g_, r_mult_b_, r_mult_a_;
+ GLint color_location_;
+ GLint colorize_color_location_;
+ GLint colorize2_color_location_;
+ GLint color_add_location_;
+ GLint reflect_mult_location_;
+ int flags_;
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_OBJECT_GL_H_
diff --git a/src/ballistica/base/graphics/gl/program/program_post_process_gl.h b/src/ballistica/base/graphics/gl/program/program_post_process_gl.h
new file mode 100644
index 00000000..64e7df99
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/program/program_post_process_gl.h
@@ -0,0 +1,327 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_POST_PROCESS_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_POST_PROCESS_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/program/program_gl.h"
+#include "ballistica/base/graphics/gl/renderer_gl.h"
+#include "ballistica/base/graphics/graphics_server.h"
+
+namespace ballistica::base {
+
+class RendererGL::ProgramPostProcessGL : public RendererGL::ProgramGL {
+ public:
+ enum TextureUnit {
+ kColorTexUnit,
+ kDepthTexUnit,
+ kColorSlightBlurredTexUnit,
+ kColorBlurredTexUnit,
+ kColorBlurredMoreTexUnit
+ };
+
+ ProgramPostProcessGL(RendererGL* renderer, int flags)
+ : RendererGL::ProgramGL(
+ renderer, Object::New(GetVertexCode(flags)),
+ Object::New(GetFragmentCode(flags)), GetName(flags),
+ GetPFlags(flags)),
+ flags_(flags),
+ dof_near_min_(0),
+ dof_near_max_(0),
+ dof_far_min_(0),
+ dof_far_max_(0),
+ distort_(0.0f) {
+ SetTextureUnit("colorTex", kColorTexUnit);
+
+ if (UsesSlightBlurredTex()) {
+ SetTextureUnit("colorSlightBlurredTex", kColorSlightBlurredTexUnit);
+ }
+ if (UsesBlurredTexture()) {
+ SetTextureUnit("colorBlurredTex", kColorBlurredTexUnit);
+ }
+ SetTextureUnit("colorBlurredMoreTex", kColorBlurredMoreTexUnit);
+ SetTextureUnit("depthTex", kDepthTexUnit);
+
+ dof_location_ = glGetUniformLocation(program(), "dofRange");
+ assert(dof_location_ != -1);
+
+ if (flags & SHD_DISTORT) {
+ distort_location_ = glGetUniformLocation(program(), "distort");
+ assert(distort_location_ != -1);
+ }
+ }
+
+ auto UsesSlightBlurredTex() -> bool {
+ return static_cast(flags_ & SHD_EYES);
+ }
+ auto UsesBlurredTexture() -> bool {
+ return static_cast(flags_ & (SHD_HIGHER_QUALITY | SHD_EYES));
+ }
+ void SetColorTexture(GLuint t) {
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kColorTexUnit);
+ }
+ void SetColorSlightBlurredTexture(GLuint t) {
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kColorSlightBlurredTexUnit);
+ }
+ void SetColorBlurredMoreTexture(GLuint t) {
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kColorBlurredMoreTexUnit);
+ }
+ void SetColorBlurredTexture(GLuint t) {
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kColorBlurredTexUnit);
+ }
+ void SetDepthTexture(GLuint t) {
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kDepthTexUnit);
+ }
+
+ void SetDepthOfFieldRanges(float near_min, float near_max, float far_min,
+ float far_max) {
+ assert(IsBound());
+ if (near_min != dof_near_min_ || near_max != dof_near_max_
+ || far_min != dof_far_min_ || far_max != dof_far_max_) {
+ BA_DEBUG_CHECK_GL_ERROR;
+ dof_near_min_ = near_min;
+ dof_near_max_ = near_max;
+ dof_far_min_ = far_min;
+ dof_far_max_ = far_max;
+ float vals[4] = {dof_near_min_, dof_near_max_, dof_far_min_,
+ dof_far_max_};
+ glUniform1fv(dof_location_, 4, vals);
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+ }
+
+ void SetDistort(float distort) {
+ assert(IsBound());
+ assert(flags_ & SHD_DISTORT);
+ if (distort != distort_) {
+ BA_DEBUG_CHECK_GL_ERROR;
+ distort_ = distort;
+ glUniform1f(distort_location_, distort_);
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+ }
+
+ private:
+ auto GetName(int flags) -> std::string {
+ return std::string("PostProcessProgramGL");
+ }
+
+ auto GetPFlags(int flags) -> int {
+ int pflags = PFLAG_USES_POSITION_ATTR;
+ if (flags & SHD_DISTORT) {
+ pflags |= (PFLAG_USES_NORMAL_ATTR | PFLAG_USES_MODEL_VIEW_MATRIX);
+ }
+ return pflags;
+ }
+
+ auto GetVertexCode(int flags) -> std::string {
+ std::string s;
+ s = "uniform mat4 modelViewProjectionMatrix;\n" BA_GLSL_VERTEX_IN
+ " vec4 position;\n";
+ if (flags & SHD_DISTORT)
+ s += BA_GLSL_VERTEX_IN " " BA_GLSL_LOWP
+ "vec3 normal;\n"
+ "uniform mat4 modelViewMatrix;\n"
+ "uniform float distort;\n";
+ if (flags & SHD_EYES) {
+ s += BA_GLSL_VERTEX_OUT " " BA_GLSL_HIGHP "float calcedDepth;\n";
+ }
+
+ s += BA_GLSL_VERTEX_OUT
+ " " BA_GLSL_MEDIUMP
+ "vec4 vScreenCoord;\n"
+ "void main() {\n"
+ " gl_Position = modelViewProjectionMatrix*position;\n";
+ if (flags & SHD_DISTORT) {
+ s += " float eyeDot = "
+ "abs(normalize(modelViewMatrix*vec4(normal,0.0))).z;\n"
+ " vec4 posDistorted = "
+ "modelViewProjectionMatrix*(position-eyeDot*distort*vec4(normal,0));"
+ "\n"
+ " vScreenCoord = "
+ "vec4(posDistorted.xy/posDistorted.w,posDistorted.zw);\n"
+ " vScreenCoord.xy += vec2(1.0);\n"
+ " vScreenCoord.xy *= vec2(0.5*vScreenCoord.w);\n";
+ } else {
+ s += " vScreenCoord = "
+ "vec4(gl_Position.xy/gl_Position.w,gl_Position.zw);\n"
+ " vScreenCoord.xy += vec2(1.0);\n"
+ " vScreenCoord.xy *= vec2(0.5*vScreenCoord.w);\n";
+ }
+ if (flags & SHD_EYES) {
+ s += " calcedDepth = " + std::to_string(kBackingDepth3) + "+"
+ + std::to_string(kBackingDepth4 - kBackingDepth3)
+ + "*(0.5*(gl_Position.z/gl_Position.w)+0.5);\n";
+ }
+ s += "}";
+
+ if (flags & SHD_DEBUG_PRINT)
+ Log(LogLevel::kInfo,
+ "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s);
+ return s;
+ }
+
+ auto GetFragmentCode(int flags) -> std::string {
+ std::string s;
+ s = "uniform " BA_GLSL_LOWP
+ "sampler2D colorTex;\n"
+ "uniform " BA_GLSL_LOWP
+ "sampler2D colorBlurredMoreTex;\n"
+ "uniform " BA_GLSL_HIGHP "sampler2D depthTex;\n" BA_GLSL_FRAG_IN
+ " " BA_GLSL_MEDIUMP
+ "vec4 vScreenCoord;\n"
+ "uniform " BA_GLSL_LOWP "float dofRange[4];\n";
+ if (flags & (SHD_HIGHER_QUALITY | SHD_EYES)) {
+ s += "uniform " BA_GLSL_LOWP "sampler2D colorBlurredTex;\n";
+ }
+ if (flags & SHD_EYES) {
+ s += "uniform " BA_GLSL_LOWP
+ "sampler2D colorSlightBlurredTex;\n" BA_GLSL_FRAG_IN
+ " " BA_GLSL_HIGHP "float calcedDepth;\n";
+ }
+
+ s += "void main() {\n"
+ " " BA_GLSL_MEDIUMP "float depth = " BA_GLSL_TEXTURE2DPROJ
+ "(depthTex,vScreenCoord).r;\n";
+
+ bool doConditional = ((flags & SHD_CONDITIONAL) && !(flags & (SHD_EYES)));
+
+ if (doConditional) {
+ // special-case completely out of focus areas and completely in-focus
+ // areas.
+ s += " if (depth > dofRange[1] && depth < dofRange[2]) {\n";
+ if (flags & SHD_HIGHER_QUALITY) {
+ s += " " BA_GLSL_LOWP "vec4 color = " BA_GLSL_TEXTURE2DPROJ
+ "(colorTex,vScreenCoord);\n"
+ " " BA_GLSL_LOWP "vec4 colorBlurred = " BA_GLSL_TEXTURE2DPROJ
+ "(colorBlurredTex,vScreenCoord);\n"
+ " " BA_GLSL_LOWP
+ "vec4 colorBlurredMore = "
+ "0.4*" BA_GLSL_TEXTURE2DPROJ
+ "(colorBlurredMoreTex,vScreenCoord);\n"
+ " " BA_GLSL_MEDIUMP
+ "vec4 diff = colorBlurred-color;\n"
+ " diff = sign(diff) * max(vec4(0.0),abs(diff)-0.12);\n"
+ " " BA_GLSL_FRAGCOLOR
+ " = (0.55*colorBlurredMore) + "
+ "(0.62+colorBlurredMore)*(color-diff);\n\n";
+ } else {
+ s += " " BA_GLSL_FRAGCOLOR " = " BA_GLSL_TEXTURE2DPROJ
+ "(colorTex,vScreenCoord);\n";
+ }
+ s += " }\n"
+ " else if (depth < dofRange[0] || depth > dofRange[3]) {\n";
+ if (flags & SHD_HIGHER_QUALITY) {
+ s += " " BA_GLSL_LOWP "vec4 colorBlurred = " BA_GLSL_TEXTURE2DPROJ
+ "(colorBlurredTex,vScreenCoord);\n"
+ " " BA_GLSL_LOWP
+ "vec4 colorBlurredMore = "
+ "0.4*" BA_GLSL_TEXTURE2DPROJ
+ "(colorBlurredMoreTex,vScreenCoord);\n"
+ " " BA_GLSL_FRAGCOLOR
+ " = (0.55*colorBlurredMore) + "
+ "(0.62+colorBlurredMore)*colorBlurred;\n\n";
+ } else {
+ s += " " BA_GLSL_FRAGCOLOR
+ " = "
+ "" BA_GLSL_TEXTURE2DPROJ "(colorBlurredMoreTex,vScreenCoord);\n";
+ }
+ s += " }\n"
+ " else{\n";
+ }
+
+ // Transition areas.
+ s += " " BA_GLSL_LOWP "vec4 color = " BA_GLSL_TEXTURE2DPROJ
+ "(colorTex,vScreenCoord);\n";
+ if (flags & SHD_EYES)
+ s += " " BA_GLSL_LOWP
+ "vec4 colorSlightBlurred = "
+ "" BA_GLSL_TEXTURE2DPROJ "(colorSlightBlurredTex,vScreenCoord);\n";
+
+// FIXME: Should make proper blur work in VR (perhaps just pass a uniform?
+// FIXME2: This will break 2D mode on the VR build.
+// #if BA_VR_BUILD
+// #define BLURSCALE "0.3 * "
+// #else
+#define BLURSCALE
+ // #endif
+
+ if (flags & (SHD_HIGHER_QUALITY | SHD_EYES)) {
+ s += " " BA_GLSL_LOWP "vec4 colorBlurred = " BA_GLSL_TEXTURE2DPROJ
+ "(colorBlurredTex,vScreenCoord);\n"
+ " " BA_GLSL_LOWP
+ "vec4 colorBlurredMore = "
+ "0.4*" BA_GLSL_TEXTURE2DPROJ
+ "(colorBlurredMoreTex,vScreenCoord);\n"
+ " " BA_GLSL_LOWP "float blur = " BLURSCALE
+ " (smoothstep(dofRange[2],dofRange[3],depth)\n"
+ " + 1.0 - "
+ "smoothstep(dofRange[0],dofRange[1],depth));\n"
+ " " BA_GLSL_MEDIUMP
+ "vec4 diff = colorBlurred-color;\n"
+ " diff = sign(diff) * max(vec4(0.0),abs(diff)-0.12);\n"
+ " " BA_GLSL_FRAGCOLOR
+ " = (0.55*colorBlurredMore) + "
+ "(0.62+colorBlurredMore)*mix(color-diff,colorBlurred,blur);\n\n";
+ } else {
+ s += " " BA_GLSL_LOWP
+ "vec4 colorBlurredMore = "
+ "" BA_GLSL_TEXTURE2DPROJ
+ "(colorBlurredMoreTex,vScreenCoord);\n"
+ " " BA_GLSL_LOWP "float blur = " BLURSCALE
+ " (smoothstep(dofRange[2],dofRange[3],depth)\n"
+ " + 1.0 - "
+ "smoothstep(dofRange[0],dofRange[1],depth));\n"
+ " " BA_GLSL_FRAGCOLOR " = mix(color,colorBlurredMore,blur);\n\n";
+ }
+
+#undef BLURSCALE
+
+ if (flags & SHD_EYES) {
+ s += " " BA_GLSL_MEDIUMP "vec4 diffEye = colorBlurred-color;\n";
+ s += " diffEye = sign(diffEye) * max(vec4(0.0),abs(diffEye)-0.06);\n";
+ s += " " BA_GLSL_LOWP
+ "vec4 baseColorEye = "
+ "mix(color-10.0*(diffEye),colorSlightBlurred,0.83);\n";
+ s += " " BA_GLSL_LOWP
+ "vec4 eyeColor = (0.55*colorBlurredMore) + "
+ "(0.62+colorBlurredMore)*mix(baseColorEye,colorBlurred,blur);\n\n";
+ s += " " BA_GLSL_LOWP
+ "float dBlend = smoothstep(-0.0004,-0.0001,depth-calcedDepth);\n"
+ " " BA_GLSL_FRAGCOLOR " = mix(" BA_GLSL_FRAGCOLOR
+ ",eyeColor,dBlend);\n";
+ }
+ if (doConditional) {
+ s += " }\n";
+ }
+
+ // Demonstrates MSAA striation issue:
+ // s += " gl_FragColor =
+ // mix(gl_FragColor,vec4(vec3(14.0*(depth-0.76)),1),0.999);\n";
+ // s += " gl_FragColor =
+ // vec4(vec3(14.0*(" BA_GLSL_TEXTURE2DPROJ
+ // "(depthTex,vScreenCoord).r-0.76)),1);\n";
+ s += "}";
+
+ if (flags & SHD_DEBUG_PRINT)
+ Log(LogLevel::kInfo,
+ "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s);
+ return s;
+ }
+
+ int flags_;
+ float dof_near_min_;
+ float dof_near_max_;
+ float dof_far_min_;
+ float dof_far_max_;
+ GLint dof_location_;
+ float distort_;
+ GLint distort_location_;
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_POST_PROCESS_GL_H_
diff --git a/src/ballistica/base/graphics/gl/program/program_shield_gl.h b/src/ballistica/base/graphics/gl/program/program_shield_gl.h
new file mode 100644
index 00000000..9ecfd8e9
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/program/program_shield_gl.h
@@ -0,0 +1,128 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_SHIELD_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_SHIELD_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/program/program_gl.h"
+#include "ballistica/base/graphics/gl/renderer_gl.h"
+#include "ballistica/base/graphics/graphics_server.h"
+
+namespace ballistica::base {
+
+class RendererGL::ProgramShieldGL : public RendererGL::ProgramGL {
+ public:
+ enum TextureUnit {
+ kDepthTexUnit,
+ };
+
+ ProgramShieldGL(RendererGL* renderer, int flags)
+ : RendererGL::ProgramGL(
+ renderer, Object::New(GetVertexCode(flags)),
+ Object::New(GetFragmentCode(flags)), GetName(flags),
+ GetPFlags(flags)),
+ flags_(flags) {
+ SetTextureUnit("depthTex", kDepthTexUnit);
+ }
+ void SetDepthTexture(GLuint t) {
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kDepthTexUnit);
+ }
+
+ private:
+ auto GetName(int flags) -> std::string {
+ return std::string("ShieldProgramGL");
+ }
+
+ auto GetPFlags(int flags) -> int {
+ int pflags = PFLAG_USES_POSITION_ATTR;
+ return pflags;
+ }
+
+ auto GetVertexCode(int flags) -> std::string {
+ std::string s;
+ s = "uniform mat4 modelViewProjectionMatrix;\n" BA_GLSL_VERTEX_IN
+ " vec4 position;\n" BA_GLSL_VERTEX_OUT " " BA_GLSL_HIGHP
+ "vec4 vScreenCoord;\n"
+ "void main() {\n"
+ " gl_Position = modelViewProjectionMatrix * position;\n"
+ " vScreenCoord = vec4(gl_Position.xy / gl_Position.w,"
+ " gl_Position.zw);\n"
+ " vScreenCoord.xy += vec2(1.0);\n"
+ " vScreenCoord.xy *= vec2(0.5 * vScreenCoord.w);\n";
+ s += "}";
+
+ if (flags & SHD_DEBUG_PRINT)
+ Log(LogLevel::kInfo,
+ "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s);
+ return s;
+ }
+
+ auto GetFragmentCode(int flags) -> std::string {
+ std::string s;
+ s = "uniform " BA_GLSL_HIGHP "sampler2D depthTex;\n" BA_GLSL_FRAG_IN
+ " " BA_GLSL_HIGHP
+ "vec4 vScreenCoord;\n"
+ "void main() {\n"
+ " " BA_GLSL_HIGHP "float depth = " BA_GLSL_TEXTURE2DPROJ
+ "(depthTex, vScreenCoord).r;\n";
+
+ // Work around Adreno bug where depth is returned as 0..1 instead of
+ // glDepthRange().
+ if (GetFunkyDepthIssue_()) {
+ s += " depth = " + std::to_string(kBackingDepth3) + " + depth * ("
+ + std::to_string(kBackingDepth4) + "-"
+ + std::to_string(kBackingDepth3) + ");\n";
+ }
+ // s+= " depth =
+ // "+std::to_string(kBackingDepth3)+"0.15+depth*(0.9-0.15);\n"; " depth
+ // *=
+ // 0.936;\n" " depth = 1.0/(65535.0*((1.0/depth)/16777216.0));\n" " depth
+ //= 1.0/((1.0/depth)+0.08);\n" " depth += 0.1f;\n"
+ s += " " BA_GLSL_HIGHP
+ "float d = abs(depth - gl_FragCoord.z);\n"
+ " d = 1.0 - smoothstep(0.0, 0.0006, d);\n"
+ " d = 0.2 * smoothstep(0.96, 1.0, d)"
+ " + 0.2 * d + 0.4 * d * d * d;\n";
+
+ // Some mali chips seem to have no high precision and thus this looks
+ // terrible; in those cases lets done down the intersection effect
+ // significantly
+ // if (GetDrawsShieldsFunny_()) {
+ // s += " " BA_GLSL_FRAGCOLOR " = vec4(d*0.13,d*0.1,d,0);\n";
+ // } else {
+ s += " " BA_GLSL_FRAGCOLOR " = vec4(d*0.5, d*0.4, d, 0);\n";
+ // }
+ s += "}";
+
+ // This shows msaa depth error on bridgit.
+ //" " BA_GLSL_FRAGCOLOR " =
+ // vec4(smoothstep(0.73,0.77,depth),0.0,0.0,0.5);\n"
+
+ // " d = 1.0 - smoothstep(0.0,0.0006,d);\n"
+ // " d = 0.2*smoothstep(0.96,1.0,d)+0.2*d+0.4*d*d*d;\n"
+ //" if (d < 0.01) " BA_GLSL_FRAGCOLOR " = vec4(0.0,1.0,0.0,0.5);\n"
+
+ //" " BA_GLSL_FRAGCOLOR " =
+ // vec4(vec3(10.0*abs(depth-gl_FragCoord.z)),1);\n"
+ // " " BA_GLSL_FRAGCOLOR " =
+ // vec4(0,10.0*abs(depth-gl_FragCoord.z),0,0.1);\n" " if (depth <
+ // gl_FragCoord.z) " BA_GLSL_FRAGCOLOR " =
+ // vec4(1.0-10.0*(gl_FragCoord.z-depth),0,0,1);\n" " else "
+ // BA_GLSL_FRAGCOLOR " = vec4(0,1.0-10.0*(depth-gl_FragCoord.z),0,1);\n"
+ //" " BA_GLSL_FRAGCOLOR " = vec4(vec3(depth),1);\n"
+
+ if (flags & SHD_DEBUG_PRINT)
+ Log(LogLevel::kInfo,
+ "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s);
+ return s;
+ }
+
+ int flags_;
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_SHIELD_GL_H_
diff --git a/src/ballistica/base/graphics/gl/program/program_simple_gl.h b/src/ballistica/base/graphics/gl/program/program_simple_gl.h
new file mode 100644
index 00000000..e2b27d67
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/program/program_simple_gl.h
@@ -0,0 +1,413 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_SIMPLE_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_SIMPLE_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/program/program_gl.h"
+#include "ballistica/base/graphics/gl/renderer_gl.h"
+#include "ballistica/base/graphics/graphics_server.h"
+
+namespace ballistica::base {
+class RendererGL::ProgramSimpleGL : public RendererGL::ProgramGL {
+ public:
+ enum TextureUnit {
+ kColorTexUnit,
+ kColorizeTexUnit,
+ kMaskTexUnit,
+ kMaskUV2TexUnit,
+ kBlurTexUnit
+ };
+
+ ProgramSimpleGL(RendererGL* renderer, int flags)
+ : RendererGL::ProgramGL(
+ renderer, Object::New(GetVertexCode(flags)),
+ Object::New(GetFragmentCode(flags)), GetName(flags),
+ GetPFlags(flags)),
+ flags_(flags) {
+ if (flags & SHD_TEXTURE) {
+ SetTextureUnit("colorTex", kColorTexUnit);
+ }
+ if (flags & SHD_COLORIZE) {
+ SetTextureUnit("colorizeTex", kColorizeTexUnit);
+ colorize_color_location_ =
+ glGetUniformLocation(program(), "colorizeColor");
+ assert(colorize_color_location_ != -1);
+ }
+ if (flags & SHD_COLORIZE2) {
+ colorize2_color_location_ =
+ glGetUniformLocation(program(), "colorize2Color");
+ assert(colorize2_color_location_ != -1);
+ }
+ if ((!(flags & SHD_TEXTURE)) || (flags & SHD_MODULATE)) {
+ color_location_ = glGetUniformLocation(program(), "color");
+ assert(color_location_ != -1);
+ }
+ if (flags & SHD_SHADOW) {
+ shadow_params_location_ = glGetUniformLocation(program(), "shadowParams");
+ assert(shadow_params_location_ != -1);
+ }
+ if (flags & SHD_GLOW) {
+ glow_params_location_ = glGetUniformLocation(program(), "glowParams");
+ assert(glow_params_location_ != -1);
+ }
+ if (flags & SHD_FLATNESS) {
+ flatness_location = glGetUniformLocation(program(), "flatness");
+ assert(flatness_location != -1);
+ }
+ if (flags & SHD_MASKED) {
+ SetTextureUnit("maskTex", kMaskTexUnit);
+ }
+ if (flags & SHD_MASK_UV2) {
+ SetTextureUnit("maskUV2Tex", kMaskUV2TexUnit);
+ }
+ }
+
+ void SetColorTexture(const TextureAsset* t) {
+ assert(flags_ & SHD_TEXTURE);
+ assert(IsBound());
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kColorTexUnit);
+ }
+
+ void SetColorTexture(GLuint t) {
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kColorTexUnit);
+ }
+
+ void SetColor(float r, float g, float b, float a = 1.0f) {
+ assert((flags_ & SHD_MODULATE) || !(flags_ & SHD_TEXTURE));
+ assert(IsBound());
+ if (r != r_ || g != g_ || b != b_ || a != a_) {
+ r_ = r;
+ g_ = g;
+ b_ = b;
+ a_ = a;
+ glUniform4f(color_location_, r_, g_, b_, a_);
+ }
+ }
+
+ void SetColorizeColor(float r, float g, float b, float a = 1.0f) {
+ assert(flags_ & SHD_COLORIZE);
+ assert(IsBound());
+ if (r != colorize_r_ || g != colorize_g_ || b != colorize_b_
+ || a != colorize_a_) {
+ colorize_r_ = r;
+ colorize_g_ = g;
+ colorize_b_ = b;
+ colorize_a_ = a;
+ glUniform4f(colorize_color_location_, colorize_r_, colorize_g_,
+ colorize_b_, colorize_a_);
+ }
+ }
+
+ void SetShadow(float shadow_offset_x, float shadow_offset_y,
+ float shadow_blur, float shadow_density) {
+ assert(flags_ & SHD_SHADOW);
+ assert(IsBound());
+ if (shadow_offset_x != shadow_offset_x_
+ || shadow_offset_y != shadow_offset_y_ || shadow_blur != shadow_blur_
+ || shadow_density != shadow_density_) {
+ shadow_offset_x_ = shadow_offset_x;
+ shadow_offset_y_ = shadow_offset_y;
+ shadow_blur_ = shadow_blur;
+ shadow_density_ = shadow_density;
+ glUniform4f(shadow_params_location_, shadow_offset_x_, shadow_offset_y_,
+ shadow_blur_, shadow_density_ * 0.4f);
+ }
+ }
+
+ void SetGlow(float glow_amount, float glow_blur) {
+ assert(flags_ & SHD_GLOW);
+ assert(IsBound());
+ if (glow_amount != glow_amount_ || glow_blur != glow_blur_) {
+ glow_amount_ = glow_amount;
+ glow_blur_ = glow_blur;
+ glUniform2f(glow_params_location_, glow_amount_, glow_blur_);
+ }
+ }
+
+ void SetFlatness(float flatness) {
+ assert(flags_ & SHD_FLATNESS);
+ assert(IsBound());
+ if (flatness != flatness_) {
+ flatness_ = flatness;
+ glUniform1f(flatness_location, flatness_);
+ }
+ }
+
+ void SetColorize2Color(float r, float g, float b, float a = 1.0f) {
+ assert(flags_ & SHD_COLORIZE2);
+ assert(IsBound());
+ if (r != colorize2_r_ || g != colorize2_g_ || b != colorize2_b_
+ || a != colorize2_a_) {
+ colorize2_r_ = r;
+ colorize2_g_ = g;
+ colorize2_b_ = b;
+ colorize2_a_ = a;
+ glUniform4f(colorize2_color_location_, colorize2_r_, colorize2_g_,
+ colorize2_b_, colorize2_a_);
+ }
+ }
+
+ void SetColorizeTexture(const TextureAsset* t) {
+ assert(flags_ & SHD_COLORIZE);
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kColorizeTexUnit);
+ }
+
+ void SetMaskTexture(const TextureAsset* t) {
+ assert(flags_ & SHD_MASKED);
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kMaskTexUnit);
+ }
+
+ void SetMaskUV2Texture(const TextureAsset* t) {
+ assert(flags_ & SHD_MASK_UV2);
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kMaskUV2TexUnit);
+ }
+
+ private:
+ auto GetName(int flags) -> std::string {
+ return "SimpleProgramGL texture:"
+ + std::to_string((flags & SHD_TEXTURE) != 0)
+ + " modulate:" + std::to_string((flags & SHD_MODULATE) != 0)
+ + " colorize:" + std::to_string((flags & SHD_COLORIZE) != 0)
+ + " colorize2:" + std::to_string((flags & SHD_COLORIZE2) != 0)
+ + " premultiply:" + std::to_string((flags & SHD_PREMULTIPLY) != 0)
+ + " shadow:" + std::to_string((flags & SHD_SHADOW) != 0)
+ + " glow:" + std::to_string((flags & SHD_GLOW) != 0) + " masked:"
+ + std::to_string((flags & SHD_MASKED) != 0) + " maskedUV2:"
+ + std::to_string((flags & SHD_MASK_UV2) != 0) + " depthBugTest:"
+ + std::to_string((flags & SHD_DEPTH_BUG_TEST) != 0)
+ + " flatness:" + std::to_string((flags & SHD_FLATNESS) != 0);
+ }
+
+ auto GetPFlags(int flags) -> int {
+ int pflags = PFLAG_USES_POSITION_ATTR;
+ if (flags & SHD_TEXTURE) {
+ pflags |= PFLAG_USES_UV_ATTR;
+ }
+ if (flags & SHD_MASK_UV2) {
+ pflags |= PFLAG_USES_UV2_ATTR;
+ }
+ return pflags;
+ }
+
+ auto GetVertexCode(int flags) -> std::string {
+ // clang-format off
+ std::string s;
+ s = "uniform mat4 modelViewProjectionMatrix;\n"
+ BA_GLSL_VERTEX_IN " vec4 position;\n";
+ if ((flags & SHD_TEXTURE) || (flags & SHD_COLORIZE)
+ || (flags & SHD_COLORIZE2)) {
+ s += BA_GLSL_VERTEX_IN " vec2 uv;\n"
+ BA_GLSL_VERTEX_OUT " vec2 vUV;\n";
+ }
+ if (flags & SHD_MASK_UV2) {
+ s += BA_GLSL_VERTEX_IN " vec2 uv2;\n"
+ BA_GLSL_VERTEX_OUT " vec2 vUV2;\n";
+ }
+ if (flags & SHD_SHADOW) {
+ s += BA_GLSL_VERTEX_OUT " vec2 vUVShadow;\n"
+ BA_GLSL_VERTEX_OUT " vec2 vUVShadow2;\n"
+ BA_GLSL_VERTEX_OUT " vec2 vUVShadow3;\n"
+ "uniform " BA_GLSL_LOWP "vec4 shadowParams;\n";
+ }
+ s += "void main() {\n";
+ if (flags & SHD_TEXTURE) {
+ s += " vUV = uv;\n";
+ }
+ if (flags & SHD_MASK_UV2) {
+ s += " vUV2 = uv2;\n";
+ }
+ if (flags & SHD_SHADOW) {
+ s += " vUVShadow = uv + 0.4 *"
+ " vec2(shadowParams.x, shadowParams.y);\n";
+ }
+ if (flags & SHD_SHADOW) {
+ s += " vUVShadow2 = uv + 0.8 *"
+ " vec2(shadowParams.x, shadowParams.y);\n";
+ }
+ if (flags & SHD_SHADOW) {
+ s += " vUVShadow3 = uv + 1.3 * vec2(shadowParams.x, shadowParams.y);\n";
+ }
+ s += " gl_Position = modelViewProjectionMatrix * position;\n"
+ "}";
+
+ if (flags & SHD_DEBUG_PRINT) {
+ Log(LogLevel::kInfo,
+ "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s);
+ }
+
+ // clang-format off
+ return s;
+ }
+
+ auto GetFragmentCode(int flags) -> std::string {
+ // clang-format off
+ std::string s;
+ if (flags & SHD_TEXTURE) {
+ s += "uniform " BA_GLSL_LOWP "sampler2D colorTex;\n";
+ }
+ if ((flags & SHD_COLORIZE)) {
+ s += "uniform " BA_GLSL_LOWP "sampler2D colorizeTex;\n"
+ "uniform " BA_GLSL_LOWP "vec4 colorizeColor;\n";
+ }
+ if ((flags & SHD_COLORIZE2)) {
+ s += "uniform " BA_GLSL_LOWP "vec4 colorize2Color;\n";
+ }
+ if ((flags & SHD_TEXTURE) || (flags & SHD_COLORIZE)
+ || (flags & SHD_COLORIZE2)) {
+ s += BA_GLSL_FRAG_IN " " BA_GLSL_LOWP "vec2 vUV;\n";
+ }
+ if (flags & SHD_MASK_UV2) {
+ s += BA_GLSL_FRAG_IN " " BA_GLSL_LOWP "vec2 vUV2;\n";
+ }
+ if (flags & SHD_FLATNESS) {
+ s += "uniform " BA_GLSL_LOWP "float flatness;\n";
+ }
+ if (flags & SHD_SHADOW) {
+ s += BA_GLSL_FRAG_IN " " BA_GLSL_LOWP "vec2 vUVShadow;\n"
+ BA_GLSL_FRAG_IN " " BA_GLSL_LOWP "vec2 vUVShadow2;\n"
+ BA_GLSL_FRAG_IN " " BA_GLSL_LOWP "vec2 vUVShadow3;\n"
+ "uniform " BA_GLSL_LOWP "vec4 shadowParams;\n";
+ }
+ if (flags & SHD_GLOW) {
+ s += "uniform " BA_GLSL_LOWP "vec2 glowParams;\n";
+ }
+ if ((flags & SHD_MODULATE) || (!(flags & SHD_TEXTURE))) {
+ s += "uniform " BA_GLSL_LOWP "vec4 color;\n";
+ }
+ if (flags & SHD_MASKED) {
+ s += "uniform " BA_GLSL_LOWP "sampler2D maskTex;\n";
+ }
+ if (flags & SHD_MASK_UV2) {
+ s += "uniform " BA_GLSL_LOWP "sampler2D maskUV2Tex;\n";
+ }
+ s += "void main() {\n";
+ if (!(flags & SHD_TEXTURE)) {
+ s += " " BA_GLSL_FRAGCOLOR " = color;\n";
+ } else {
+ std::string blur_arg;
+ if (flags & SHD_GLOW) {
+ s += " " BA_GLSL_LOWP
+ "vec4 cVal = " BA_GLSL_TEXTURE2D "(colorTex, vUV, glowParams.g);\n"
+ " " BA_GLSL_FRAGCOLOR
+ " = vec4(color.rgb * cVal.rgb * cVal.a * "
+ "glowParams.r, 0.0)"; // we premultiply this.
+ if (flags & SHD_MASK_UV2) {
+ s += " * vec4(" BA_GLSL_TEXTURE2D "(maskUV2Tex, vUV2).a)";
+ }
+ s += ";\n";
+ } else {
+ if ((flags & SHD_COLORIZE) || (flags & SHD_COLORIZE2)) {
+ // TEMP TEST
+ s += " " BA_GLSL_LOWP
+ "vec4 colorizeVal = " BA_GLSL_TEXTURE2D "(colorizeTex, vUV);\n";
+ }
+ if (flags & SHD_COLORIZE) {
+ s += " " BA_GLSL_LOWP "float colorizeA = colorizeVal.r;\n";
+ }
+ if (flags & SHD_COLORIZE2) {
+ s += " " BA_GLSL_LOWP "float colorizeB = colorizeVal.g;\n";
+ }
+ if (flags & SHD_MASKED) {
+ s += " " BA_GLSL_MEDIUMP "vec4 mask = "
+ BA_GLSL_TEXTURE2D "(maskTex, vUV);";
+ }
+
+ if (flags & SHD_MODULATE) {
+ if (flags & SHD_FLATNESS) {
+ s += " " BA_GLSL_LOWP
+ "vec4 rawTexColor = " BA_GLSL_TEXTURE2D "(colorTex, vUV);\n"
+ " " BA_GLSL_FRAGCOLOR " = color * "
+ "vec4(mix(rawTexColor.rgb, vec3(1.0), flatness),"
+ " rawTexColor.a)";
+ } else {
+ s += " " BA_GLSL_FRAGCOLOR " = color * "
+ BA_GLSL_TEXTURE2D "(colorTex, vUV)";
+ }
+ } else {
+ s += " " BA_GLSL_FRAGCOLOR " = "
+ BA_GLSL_TEXTURE2D "(colorTex, vUV)";
+ }
+
+ if (flags & SHD_COLORIZE) {
+ s += " * (vec4(1.0 - colorizeA) + colorizeColor * colorizeA)";
+ }
+ if (flags & SHD_COLORIZE2) {
+ s += " * (vec4(1.0 - colorizeB) + colorize2Color * colorizeB)";
+ }
+ if (flags & SHD_MASKED) {
+ s += " * vec4(vec3(mask.r), mask.a) + "
+ "vec4(vec3(mask.g) * colorizeColor.rgb + vec3(mask.b), 0.0)";
+ }
+ s += ";\n";
+
+ if (flags & SHD_SHADOW) {
+ s += " " BA_GLSL_LOWP
+ "float shadowA = ("
+ BA_GLSL_TEXTURE2D "(colorTex, vUVShadow).a + "
+ "" BA_GLSL_TEXTURE2D "(colorTex, vUVShadow2, 1.0).a + "
+ "" BA_GLSL_TEXTURE2D
+ "(colorTex, vUVShadow3, 2.0).a) * shadowParams.a";
+
+ if (flags & SHD_MASK_UV2) {
+ s += " * " BA_GLSL_TEXTURE2D "(maskUV2Tex, vUV2).a";
+ }
+ s += ";\n";
+ s += " " BA_GLSL_FRAGCOLOR
+ " = "
+ "vec4(" BA_GLSL_FRAGCOLOR ".rgb * " BA_GLSL_FRAGCOLOR
+ ".a," BA_GLSL_FRAGCOLOR
+ ".a) + "
+ "(1.0 - " BA_GLSL_FRAGCOLOR ".a) * vec4(0, 0, 0, shadowA);\n";
+ s += " " BA_GLSL_FRAGCOLOR
+ " = "
+ "vec4(" BA_GLSL_FRAGCOLOR
+ ".rgb / max(0.001, " BA_GLSL_FRAGCOLOR ".a), "
+ BA_GLSL_FRAGCOLOR ".a);\n";
+ }
+ }
+
+ if (flags & SHD_DEPTH_BUG_TEST) {
+ s += " " BA_GLSL_FRAGCOLOR " = vec4(abs(gl_FragCoord.z - "
+ BA_GLSL_FRAGCOLOR ".r));\n";
+ }
+
+ if (flags & SHD_PREMULTIPLY) {
+ s += " " BA_GLSL_FRAGCOLOR " = vec4(" BA_GLSL_FRAGCOLOR
+ ".rgb * "
+ "" BA_GLSL_FRAGCOLOR ".a, " BA_GLSL_FRAGCOLOR ".a);";
+ }
+ }
+ s += "}";
+
+ if (flags & SHD_DEBUG_PRINT) {
+ Log(LogLevel::kInfo,
+ "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s);
+ }
+
+ // clang-format on
+ return s;
+ }
+
+ float r_{}, g_{}, b_{}, a_{};
+ float colorize_r_{}, colorize_g_{}, colorize_b_{}, colorize_a_{};
+ float colorize2_r_{}, colorize2_g_{}, colorize2_b_{}, colorize2_a_{};
+ float shadow_offset_x_{}, shadow_offset_y_{}, shadow_blur_{},
+ shadow_density_{};
+ float glow_amount_{}, glow_blur_{};
+ float flatness_{};
+ GLint color_location_{};
+ GLint colorize_color_location_{};
+ GLint colorize2_color_location_{};
+ GLint shadow_params_location_{};
+ GLint glow_params_location_{};
+ GLint flatness_location{};
+ int flags_{};
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_SIMPLE_GL_H_
diff --git a/src/ballistica/base/graphics/gl/program/program_smoke_gl.h b/src/ballistica/base/graphics/gl/program/program_smoke_gl.h
new file mode 100644
index 00000000..dc6f2bbc
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/program/program_smoke_gl.h
@@ -0,0 +1,176 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_SMOKE_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_SMOKE_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/program/program_gl.h"
+#include "ballistica/base/graphics/gl/renderer_gl.h"
+#include "ballistica/base/graphics/graphics_server.h"
+
+namespace ballistica::base {
+
+class RendererGL::ProgramSmokeGL : public RendererGL::ProgramGL {
+ public:
+ enum TextureUnit { kColorTexUnit, kDepthTexUnit, kBlurTexUnit };
+
+ ProgramSmokeGL(RendererGL* renderer, int flags)
+ : RendererGL::ProgramGL(
+ renderer, Object::New(GetVertexCode(flags)),
+ Object::New(GetFragmentCode(flags)), GetName(flags),
+ GetPFlags(flags)),
+ flags_(flags),
+ r_(0),
+ g_(0),
+ b_(0),
+ a_(0) {
+ SetTextureUnit("colorTex", kColorTexUnit);
+ if (flags & SHD_OVERLAY) {
+ SetTextureUnit("depthTex", kDepthTexUnit);
+ SetTextureUnit("blurTex", kBlurTexUnit);
+ }
+ color_location_ = glGetUniformLocation(program(), "colorMult");
+ assert(color_location_ != -1);
+ }
+
+ void SetColorTexture(const TextureAsset* t) {
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kColorTexUnit);
+ }
+
+ void SetDepthTexture(GLuint t) {
+ assert(flags_ & SHD_OVERLAY);
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kDepthTexUnit);
+ }
+
+ void SetBlurTexture(GLuint t) {
+ assert(flags_ & SHD_OVERLAY);
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kBlurTexUnit);
+ }
+
+ void SetColor(float r, float g, float b, float a = 1.0f) {
+ assert(IsBound());
+ // include tint..
+ if (r * renderer()->tint().x != r_ || g * renderer()->tint().y != g_
+ || b * renderer()->tint().z != b_ || a != a_) {
+ r_ = r * renderer()->tint().x;
+ g_ = g * renderer()->tint().y;
+ b_ = b * renderer()->tint().z;
+ a_ = a;
+ glUniform4f(color_location_, r_, g_, b_, a_);
+ }
+ }
+
+ private:
+ auto GetName(int flags) -> std::string {
+ return std::string("SmokeProgramGL");
+ }
+
+ auto GetPFlags(int flags) -> int {
+ int pflags = PFLAG_USES_POSITION_ATTR | PFLAG_USES_DIFFUSE_ATTR
+ | PFLAG_USES_UV_ATTR | PFLAG_WORLD_SPACE_PTS
+ | PFLAG_USES_ERODE_ATTR | PFLAG_USES_COLOR_ATTR;
+ return pflags;
+ }
+
+ auto GetVertexCode(int flags) -> std::string {
+ std::string s;
+ s = "uniform mat4 modelViewProjectionMatrix;\n" BA_GLSL_VERTEX_IN
+ " vec4 position;\n" BA_GLSL_VERTEX_IN " " BA_GLSL_MEDIUMP
+ "vec2 uv;\n" BA_GLSL_VERTEX_OUT " " BA_GLSL_MEDIUMP
+ "vec2 vUV;\n" BA_GLSL_VERTEX_IN " " BA_GLSL_LOWP
+ "float erode;\n" BA_GLSL_VERTEX_IN " " BA_GLSL_MEDIUMP
+ "float diffuse;\n" BA_GLSL_VERTEX_OUT " " BA_GLSL_LOWP
+ "float vErode;\n" BA_GLSL_VERTEX_IN " " BA_GLSL_MEDIUMP
+ "vec4 color;\n" BA_GLSL_VERTEX_OUT " " BA_GLSL_LOWP
+ "vec4 vColor;\n"
+ "uniform " BA_GLSL_MEDIUMP "vec4 colorMult;\n";
+ if (flags & SHD_OVERLAY) {
+ s += BA_GLSL_VERTEX_OUT " " BA_GLSL_LOWP
+ "vec4 cDiffuse;\n" BA_GLSL_VERTEX_OUT
+ " " BA_GLSL_MEDIUMP "vec4 vScreenCoord;\n";
+ }
+ s += "void main() {\n"
+ " vUV = uv;\n"
+ " gl_Position = modelViewProjectionMatrix*position;\n"
+ " vErode = erode;\n";
+ // in overlay mode we pass color/diffuse to the pixel-shader since we
+ // combine them there with a blurred bg image to get a soft look. In the
+ // simple version we just use a flat ambient color here.
+ if (flags & SHD_OVERLAY) {
+ s += " vScreenCoord = "
+ "vec4(gl_Position.xy/gl_Position.w,gl_Position.zw);\n"
+ " vColor = vec4(vec3(7.0*diffuse),0.7) * color * colorMult;\n"
+ " cDiffuse = colorMult*(0.3+0.8*diffuse);\n"
+ " vScreenCoord.xy += vec2(1.0);\n"
+ " vScreenCoord.xy *= vec2(0.5*vScreenCoord.w);\n";
+ } else {
+ s += " vColor = "
+ "(vec4(vec3(7.0),1.0)*color+vec4(vec3(0.4),0))*vec4(vec3(diffuse),0."
+ "4) * colorMult;\n";
+ }
+ s += " vColor *= vec4(vec3(vColor.a),1.0);\n"; // premultiply
+ s += "}";
+
+ if (flags & SHD_DEBUG_PRINT)
+ Log(LogLevel::kInfo,
+ "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s);
+ return s;
+ }
+
+ auto GetFragmentCode(int flags) -> std::string {
+ std::string s;
+ s = "uniform " BA_GLSL_LOWP "sampler2D colorTex;\n" BA_GLSL_FRAG_IN
+ " " BA_GLSL_MEDIUMP "vec2 vUV;\n" BA_GLSL_FRAG_IN " " BA_GLSL_LOWP
+ "float vErode;\n" BA_GLSL_FRAG_IN " " BA_GLSL_LOWP "vec4 vColor;\n";
+ if (flags & SHD_OVERLAY) {
+ s += BA_GLSL_FRAG_IN " " BA_GLSL_MEDIUMP
+ "vec4 vScreenCoord;\n"
+ "uniform " BA_GLSL_LOWP
+ "sampler2D depthTex;\n"
+ "uniform " BA_GLSL_LOWP
+ "sampler2D blurTex;\n" BA_GLSL_FRAG_IN
+ " " BA_GLSL_LOWP "vec4 cDiffuse;\n";
+ }
+ s += "void main() {\n";
+ s += " " BA_GLSL_LOWP
+ "float erodeMult = smoothstep(vErode,1.0," BA_GLSL_TEXTURE2D
+ "(colorTex,vUV).r);\n"
+ " " BA_GLSL_FRAGCOLOR " = (vColor*vec4(erodeMult));";
+ if (flags & SHD_OVERLAY) {
+ s += " " BA_GLSL_FRAGCOLOR " += vec4(vec3(" BA_GLSL_FRAGCOLOR
+ ".a),0) * cDiffuse * "
+ "(0.11+0.8*" BA_GLSL_TEXTURE2DPROJ "(blurTex,vScreenCoord));\n";
+ s += " " BA_GLSL_MEDIUMP " float depth =" BA_GLSL_TEXTURE2DPROJ
+ "(depthTex,vScreenCoord).r;\n";
+
+ // Work around Adreno bug where depth is returned as 0..1 instead of
+ // glDepthRange().
+ if (GetFunkyDepthIssue_()) {
+ s += " depth = " + std::to_string(kBackingDepth3) + "+depth*("
+ + std::to_string(kBackingDepth4) + "-"
+ + std::to_string(kBackingDepth3) + ");\n";
+ }
+ s += " " BA_GLSL_FRAGCOLOR
+ " *= "
+ "(1.0-smoothstep(0.0,0.002,gl_FragCoord.z-depth));\n";
+ }
+
+ s += "}";
+
+ if (flags & SHD_DEBUG_PRINT)
+ Log(LogLevel::kInfo,
+ "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s);
+ return s;
+ }
+
+ float r_, g_, b_, a_;
+ GLint color_location_;
+ int flags_;
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_SMOKE_GL_H_
diff --git a/src/ballistica/base/graphics/gl/program/program_sprite_gl.h b/src/ballistica/base/graphics/gl/program/program_sprite_gl.h
new file mode 100644
index 00000000..16f34dbd
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/program/program_sprite_gl.h
@@ -0,0 +1,180 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_SPRITE_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_SPRITE_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/program/program_gl.h"
+#include "ballistica/base/graphics/gl/renderer_gl.h"
+#include "ballistica/base/graphics/graphics_server.h"
+
+namespace ballistica::base {
+
+class RendererGL::ProgramSpriteGL : public RendererGL::ProgramGL {
+ public:
+ enum TextureUnit { kColorTexUnit, kDepthTexUnit };
+
+ ProgramSpriteGL(RendererGL* renderer, int flags)
+ : RendererGL::ProgramGL(
+ renderer, Object::New(GetVertexCode(flags)),
+ Object::New(GetFragmentCode(flags)), GetName(flags),
+ GetPFlags(flags)),
+ flags_(flags),
+ r_(0),
+ g_(0),
+ b_(0),
+ a_(0) {
+ SetTextureUnit("colorTex", kColorTexUnit);
+
+ if (flags & SHD_OVERLAY) {
+ SetTextureUnit("depthTex", kDepthTexUnit);
+ }
+
+ if (flags & SHD_COLOR) {
+ color_location_ = glGetUniformLocation(program(), "colorU");
+ assert(color_location_ != -1);
+ }
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+
+ void SetColorTexture(const TextureAsset* t) {
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kColorTexUnit);
+ }
+
+ void SetDepthTexture(GLuint t) {
+ assert(flags_ & SHD_OVERLAY);
+ renderer()->BindTexture_(GL_TEXTURE_2D, t, kDepthTexUnit);
+ }
+
+ void SetColor(float r, float g, float b, float a = 1.0f) {
+ assert(flags_ & SHD_COLOR);
+ assert(IsBound());
+ if (r != r_ || g != g_ || b != b_ || a != a_) {
+ r_ = r;
+ g_ = g;
+ b_ = b;
+ a_ = a;
+ glUniform4f(color_location_, r_, g_, b_, a_);
+ }
+ }
+
+ private:
+ auto GetName(int flags) -> std::string {
+ return std::string("SpriteProgramGL");
+ }
+
+ auto GetPFlags(int flags) -> int {
+ int pflags = PFLAG_USES_POSITION_ATTR | PFLAG_USES_SIZE_ATTR
+ | PFLAG_USES_COLOR_ATTR | PFLAG_USES_UV_ATTR;
+ if (flags & SHD_CAMERA_ALIGNED) pflags |= PFLAG_USES_CAM_ORIENT_MATRIX;
+ return pflags;
+ }
+
+ auto GetVertexCode(int flags) -> std::string {
+ std::string s;
+ s += "uniform mat4 modelViewProjectionMatrix;\n" BA_GLSL_VERTEX_IN
+ " vec4 position;\n" BA_GLSL_VERTEX_IN " " BA_GLSL_MEDIUMP
+ "vec2 uv;\n" BA_GLSL_VERTEX_IN " " BA_GLSL_MEDIUMP
+ "float size;\n" BA_GLSL_VERTEX_OUT " " BA_GLSL_MEDIUMP "vec2 vUV;\n";
+
+ if (flags & SHD_COLOR) {
+ s += "uniform " BA_GLSL_LOWP "vec4 colorU;\n";
+ }
+
+ if (flags & SHD_CAMERA_ALIGNED) {
+ s += "uniform mat4 camOrientMatrix;\n";
+ }
+
+ if (flags & SHD_OVERLAY) {
+ s += BA_GLSL_VERTEX_OUT " " BA_GLSL_LOWP "vec4 vScreenCoord;\n";
+ }
+
+ s += BA_GLSL_VERTEX_IN " " BA_GLSL_LOWP "vec4 color;\n" BA_GLSL_VERTEX_OUT
+ " " BA_GLSL_LOWP
+ "vec4 vColor;\n"
+ "void main() {\n";
+
+ if (flags & SHD_CAMERA_ALIGNED) {
+ s += " " BA_GLSL_HIGHP
+ "vec4 pLocal = "
+ "(position+camOrientMatrix*vec4((uv.s-0.5)*size,0,(uv.t-0.5)*size,0)"
+ ");\n";
+ } else {
+ s += " " BA_GLSL_HIGHP
+ "vec4 pLocal = "
+ "(position+vec4((uv.s-0.5)*size,0,(uv.t-0.5)*size,0));\n";
+ }
+ s += " gl_Position = modelViewProjectionMatrix*pLocal;\n"
+ " vUV = uv;\n";
+ if (flags & SHD_COLOR) {
+ s += " vColor = color*colorU;\n";
+ } else {
+ s += " vColor = color;\n";
+ }
+ if (flags & SHD_OVERLAY)
+ s += " vScreenCoord = "
+ "vec4(gl_Position.xy/gl_Position.w,gl_Position.zw);\n"
+ " vScreenCoord.xy += vec2(1.0);\n"
+ " vScreenCoord.xy *= vec2(0.5*vScreenCoord.w);\n";
+ s += "}";
+
+ if (flags & SHD_DEBUG_PRINT) {
+ Log(LogLevel::kInfo,
+ "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s);
+ }
+ return s;
+ }
+
+ auto GetFragmentCode(int flags) -> std::string {
+ std::string s;
+
+ s += "uniform " BA_GLSL_LOWP "sampler2D colorTex;\n" BA_GLSL_FRAG_IN
+ " " BA_GLSL_MEDIUMP "vec2 vUV;\n" BA_GLSL_FRAG_IN " " BA_GLSL_LOWP
+ "vec4 vColor;\n";
+ if (flags & SHD_OVERLAY) {
+ s += BA_GLSL_FRAG_IN " " BA_GLSL_MEDIUMP
+ "vec4 vScreenCoord;\n"
+ "uniform " BA_GLSL_MEDIUMP "sampler2D depthTex;\n";
+ }
+
+ s += "void main() {\n"
+ " " BA_GLSL_FRAGCOLOR " = vColor*vec4(" BA_GLSL_TEXTURE2D
+ "(colorTex,vUV).r);\n";
+ if (flags & SHD_EXP2)
+ s += " " BA_GLSL_FRAGCOLOR
+ " = vec4(vUV,0,0) + "
+ "vec4(" BA_GLSL_FRAGCOLOR ".rgb*" BA_GLSL_FRAGCOLOR
+ ".rgb," BA_GLSL_FRAGCOLOR ".a);\n";
+ if (flags & SHD_OVERLAY) {
+ s += " " BA_GLSL_MEDIUMP "float depth = " BA_GLSL_TEXTURE2DPROJ
+ "(depthTex,vScreenCoord).r;\n";
+ // Adreno 320 bug where depth is returned as 0..1 instead of
+ // glDepthRange().
+ if (GetFunkyDepthIssue_()) {
+ s += " depth = " + std::to_string(kBackingDepth3) + "+depth*("
+ + std::to_string(kBackingDepth4) + "-"
+ + std::to_string(kBackingDepth3) + ");\n";
+ }
+ s += " " BA_GLSL_FRAGCOLOR
+ " *= "
+ "(1.0-smoothstep(0.0,0.001,gl_FragCoord.z-depth));\n";
+ }
+ s += "}";
+ if (flags & SHD_DEBUG_PRINT) {
+ Log(LogLevel::kInfo,
+ "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s);
+ }
+ return s;
+ }
+
+ float r_, g_, b_, a_;
+ GLint color_location_;
+ int flags_;
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_PROGRAM_PROGRAM_SPRITE_GL_H_
diff --git a/src/ballistica/base/graphics/gl/render_target_gl.h b/src/ballistica/base/graphics/gl/render_target_gl.h
new file mode 100644
index 00000000..935fdd17
--- /dev/null
+++ b/src/ballistica/base/graphics/gl/render_target_gl.h
@@ -0,0 +1,129 @@
+// Released under the MIT License. See LICENSE for details.
+
+#ifndef BALLISTICA_BASE_GRAPHICS_GL_RENDER_TARGET_GL_H_
+#define BALLISTICA_BASE_GRAPHICS_GL_RENDER_TARGET_GL_H_
+
+#if BA_ENABLE_OPENGL
+
+#include "ballistica/base/graphics/gl/framebuffer_object_gl.h"
+
+namespace ballistica::base {
+
+class RendererGL::RenderTargetGL : public RenderTarget {
+ public:
+ void Bind() {
+ if (type_ == Type::kFramebuffer) {
+ assert(framebuffer_.Exists());
+ framebuffer_->Bind();
+ } else {
+ assert(type_ == Type::kScreen);
+ renderer_->BindFramebuffer(renderer_->screen_framebuffer_);
+ }
+ }
+
+ void DrawBegin(bool must_clear_color, float clear_r, float clear_g,
+ float clear_b, float clear_a) override {
+ assert(g_base->InGraphicsThread());
+ BA_DEBUG_CHECK_GL_ERROR;
+
+ Bind();
+
+#if BA_CARDBOARD_BUILD
+ int x, y;
+ // Viewport offsets only apply to the screen render-target.
+ if (type_ == Type::kScreen) {
+ x = renderer_->VRGetViewportX();
+ y = renderer_->VRGetViewportY();
+ } else {
+ x = 0;
+ y = 0;
+ }
+ renderer_->SetViewport_(x, y, physical_width_, physical_height_);
+#else
+ renderer_->SetViewport_(0, 0, static_cast(physical_width_),
+ static_cast(physical_height_));
+#endif
+
+ {
+ // Clear depth, color, etc.
+ GLuint clear_mask = 0;
+
+ // If they *requested* a clear for color, do so. Otherwise invalidate.
+ if (must_clear_color) {
+ clear_mask |= GL_COLOR_BUFFER_BIT;
+ } else {
+ renderer_->InvalidateFramebuffer(true, false, false);
+ }
+
+ if (depth_) {
+ // FIXME make sure depth writing is turned on at this point.
+ // this needs to be on for glClear to work on depth.
+ if (!renderer_->depth_writing_enabled_) {
+ BA_LOG_ONCE(
+ LogLevel::kWarning,
+ "RendererGL: depth-writing not enabled when clearing depth");
+ }
+ clear_mask |= GL_DEPTH_BUFFER_BIT;
+ }
+
+ if (clear_mask != 0) {
+ if (clear_mask & GL_COLOR_BUFFER_BIT) {
+ glClearColor(clear_r, clear_g, clear_b, clear_a);
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+ glClear(clear_mask);
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+ }
+ }
+
+ auto GetFramebufferID() -> GLuint {
+ if (type_ == Type::kFramebuffer) {
+ assert(framebuffer_.Exists());
+ return framebuffer_->id();
+ } else {
+ return 0; // screen
+ }
+ }
+
+ auto framebuffer() -> FramebufferObjectGL* {
+ assert(type_ == Type::kFramebuffer);
+ return framebuffer_.Get();
+ }
+
+ // Screen constructor.
+ explicit RenderTargetGL(RendererGL* renderer)
+ : RenderTarget(Type::kScreen), renderer_(renderer) {
+ assert(g_base->InGraphicsThread());
+ depth_ = true;
+
+ // This will update our width/height values.
+ OnScreenSizeChange();
+ }
+
+ // Framebuffer constructor.
+ RenderTargetGL(RendererGL* renderer, int width, int height,
+ bool linear_interp, bool depth, bool texture,
+ bool depth_texture, bool high_quality, bool msaa, bool alpha)
+ : RenderTarget(Type::kFramebuffer), renderer_(renderer) {
+ assert(g_base->InGraphicsThread());
+ BA_DEBUG_CHECK_GL_ERROR;
+ framebuffer_ = Object::New(
+ renderer, width, height, linear_interp, depth, texture, depth_texture,
+ high_quality, msaa, alpha);
+ physical_width_ = static_cast(width);
+ physical_height_ = static_cast(height);
+ depth_ = depth;
+ BA_DEBUG_CHECK_GL_ERROR;
+ }
+
+ private:
+ Object::Ref framebuffer_;
+ RendererGL* renderer_{};
+};
+
+} // namespace ballistica::base
+
+#endif // BA_ENABLE_OPENGL
+
+#endif // BALLISTICA_BASE_GRAPHICS_GL_RENDER_TARGET_GL_H_
diff --git a/src/ballistica/base/graphics/gl/renderer_gl.cc b/src/ballistica/base/graphics/gl/renderer_gl.cc
index d819efc6..9fce3a61 100644
--- a/src/ballistica/base/graphics/gl/renderer_gl.cc
+++ b/src/ballistica/base/graphics/gl/renderer_gl.cc
@@ -1,67 +1,46 @@
// Released under the MIT License. See LICENSE for details.
#if BA_ENABLE_OPENGL
+
#include "ballistica/base/graphics/gl/renderer_gl.h"
-#include "ballistica/base/assets/texture_asset_preload_data.h"
-#include "ballistica/base/assets/texture_asset_renderer_data.h"
+#include
+#include
+
#include "ballistica/base/graphics/component/special_component.h"
-#include "ballistica/base/graphics/graphics_server.h"
-#include "ballistica/base/graphics/mesh/mesh_renderer_data.h"
+#include "ballistica/base/graphics/gl/framebuffer_object_gl.h"
+#include "ballistica/base/graphics/gl/gl_sys.h"
+#include "ballistica/base/graphics/gl/mesh/mesh_asset_data_gl.h"
+#include "ballistica/base/graphics/gl/mesh/mesh_data_dual_texture_full_gl.h"
+#include "ballistica/base/graphics/gl/mesh/mesh_data_gl.h"
+#include "ballistica/base/graphics/gl/mesh/mesh_data_object_split_gl.h"
+#include "ballistica/base/graphics/gl/mesh/mesh_data_simple_full_gl.h"
+#include "ballistica/base/graphics/gl/mesh/mesh_data_simple_split_gl.h"
+#include "ballistica/base/graphics/gl/mesh/mesh_data_smoke_full_gl.h"
+#include "ballistica/base/graphics/gl/mesh/mesh_data_sprite_gl.h"
+#include "ballistica/base/graphics/gl/program/program_blur_gl.h"
+#include "ballistica/base/graphics/gl/program/program_gl.h"
+#include "ballistica/base/graphics/gl/program/program_object_gl.h"
+#include "ballistica/base/graphics/gl/program/program_post_process_gl.h"
+#include "ballistica/base/graphics/gl/program/program_shield_gl.h"
+#include "ballistica/base/graphics/gl/program/program_simple_gl.h"
+#include "ballistica/base/graphics/gl/program/program_smoke_gl.h"
+#include "ballistica/base/graphics/gl/program/program_sprite_gl.h"
+#include "ballistica/base/graphics/gl/render_target_gl.h"
+#include "ballistica/base/graphics/gl/texture_data_gl.h"
#include "ballistica/base/platform/base_platform.h"
-#include "ballistica/core/core.h"
+#include "ballistica/shared/buildconfig/buildconfig_common.h"
#if BA_OSTYPE_IOS_TVOS
-#include "ballistica/core/platform/apple/apple_utils.h"
+#include "ballistica/base/platform/apple/apple_utils.h"
#endif
-#define MSAA_ERROR_TEST 0
-
-#if BA_OSTYPE_ANDROID
-#include
-#include
-#if !BA_USE_ES3_INCLUDES
-#include "ballistica/core/platform/android/android_gl3.h"
-#endif
-#include "ballistica/base/ui/ui.h"
-#define glDepthRange glDepthRangef
-#define glDiscardFramebufferEXT _glDiscardFramebufferEXT
-#ifndef GL_RGB565_OES
-#define GL_RGB565_OES 0x8D62
-#endif // GL_RGB565_OES
-#define GL_READ_FRAMEBUFFER 0x8CA8
-#define GL_DRAW_FRAMEBUFFER 0x8CA9
-#define GL_READ_FRAMEBUFFER_BINDING 0x8CAA
-#define glClearDepth glClearDepthf
-#endif // BA_OSTYPE_ANDROID
-
-#if BA_OSTYPE_MACOS
-#include
-#define glGenVertexArrays glGenVertexArraysAPPLE
-#define glDeleteVertexArrays glDeleteVertexArraysAPPLE
-#define glBindVertexArray glBindVertexArrayAPPLE
-#endif // BA_OSTYPE_MACOS
-
-#if BA_OSTYPE_IOS_TVOS
-void (*glInvalidateFramebuffer)(GLenum target, GLsizei num_attachments,
- const GLenum* attachments) = nullptr;
-#define glDepthRange glDepthRangef
-#define glGenVertexArrays glGenVertexArraysOES
-#define glDeleteVertexArrays glDeleteVertexArraysOES
-#define glBindVertexArray glBindVertexArrayOES
-#define glClearDepth glClearDepthf
-#endif // BA_OSTYPE_IOS_TVOS
-
// Turn this off to see how much blend overdraw is occurring.
-#define ENABLE_BLEND 1
+#define BA_GL_ENABLE_BLEND 1
// Support legacy drawing purely for debugging (should migrate this to
// post-fixed pipeline).
-#if BA_OSTYPE_MACOS
-#define ENABLE_DEBUG_DRAWING 1
-#else
-#define ENABLE_DEBUG_DRAWING 0
-#endif
+#define BA_GL_ENABLE_DEBUG_DRAW_COMMANDS 0
#ifndef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG
#define GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG 0x8C02
@@ -81,285 +60,285 @@ void (*glInvalidateFramebuffer)(GLenum target, GLsizei num_attachments,
#define GL_COMPRESSED_RGBA8_ETC2_EAC 0x9278
#endif
-#define CHECK_GL_ERROR _check_gl_error(__LINE__)
-
-// Handy to check gl stuff on opt builds.
-#define FORCE_CHECK_GL_ERRORS 0
-
-#if BA_DEBUG_BUILD || FORCE_CHECK_GL_ERRORS
-#define DEBUG_CHECK_GL_ERROR _check_gl_error(__LINE__)
-#else
-#define DEBUG_CHECK_GL_ERROR ((void)0)
-#endif
-
-// OpenGL ES uses precision.. regular GL doesn't
-#if (BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID)
-#define LOWP "lowp "
-#define MEDIUMP "mediump "
-#define HIGHP "highp "
-#else
-#define LOWP
-#define MEDIUMP
-#define HIGHP
-#endif // (BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID)
-
-// FIXME: Should make proper blur work in VR (perhaps just pass a uniform?
-#if BA_VR_BUILD
-#define BLURSCALE "0.3 * "
-#else
-#define BLURSCALE
-#endif
-
namespace ballistica::base {
-// Lots of signed bitwise stuff happening in there; should tidy it up.
-#pragma clang diagnostic push
-#pragma ide diagnostic ignored "hicpp-signed-bitwise"
-#pragma ide diagnostic ignored "bugprone-macro-parentheses"
-
bool RendererGL::funky_depth_issue_set_{};
bool RendererGL::funky_depth_issue_{};
bool RendererGL::draws_shields_funny_{};
bool RendererGL::draws_shields_funny_set_{};
-GLint g_combined_texture_image_unit_count{};
-bool g_anisotropic_support{};
-bool g_vao_support{};
-float g_max_anisotropy{};
-bool g_discard_framebuffer_support{};
-bool g_invalidate_framebuffer_support{};
-bool g_blit_framebuffer_support{};
-bool g_framebuffer_multisample_support{};
-bool g_running_es3{};
-bool g_seamless_cube_maps{};
-int g_msaa_max_samples_rgb565{};
-int g_msaa_max_samples_rgb8{};
+// FIXME - move this stuff to the renderer class.
+// GLint g_combined_texture_image_unit_count{};
+// bool g_anisotropic_support{};
+// bool g_vao_support{};
+// float g_max_anisotropy{};
+// bool g_discard_framebuffer_support{};
+// bool g_invalidate_framebuffer_support{};
+// bool g_blit_framebuffer_support{};
+// bool g_framebuffer_multisample_support{};
+// bool g_running_es3{};
+// bool g_seamless_cube_maps{};
+// int g_msaa_max_samples_rgb565{};
+// int g_msaa_max_samples_rgb8{};
#if BA_OSTYPE_ANDROID
bool RendererGL::is_speedy_android_device_{};
bool RendererGL::is_extra_speedy_android_device_{};
-#endif // BA_OSTYPE_ANDROID
+#endif
-static void _check_gl_error(int line) {
+RendererGL::RendererGL() {
+ assert(g_base->InGraphicsThread());
+
+ if (explicit_bool(BA_FORCE_CHECK_GL_ERRORS)) {
+ ScreenMessage("GL ERROR CHECKS ENABLED");
+ }
+
+ // Run any one-time setup the platform might need to do
+ // (grabbing function pointers, etc.)
+ if (!g_sys_gl_inited) {
+ SysGLInit();
+ g_sys_gl_inited = true;
+ }
+
+ CheckGLCapabilities_();
+ SyncGLState_();
+}
+
+void RendererGL::CheckGLError(const char* file, int line) {
GLenum err = glGetError();
if (err != GL_NO_ERROR) {
const char* version = (const char*)glGetString(GL_VERSION);
const char* vendor = (const char*)glGetString(GL_VENDOR);
const char* renderer = (const char*)glGetString(GL_RENDERER);
Log(LogLevel::kError,
- "OpenGL Error at line " + std::to_string(line) + ": "
- + GLErrorToString(err) + "\nrenderer: " + renderer
+ "OpenGL Error at " + std::string(file) + " line " + std::to_string(line)
+ + ": " + GLErrorToString(err) + "\nrenderer: " + renderer
+ "\nvendor: " + vendor + "\nversion: " + version
+ "\ntime: " + std::to_string(g_core->GetAppTimeMillisecs()));
}
}
-// Flags affecting shader creation.
-enum ShaderFlag {
- SHD_REFLECTION = 1,
- SHD_TEXTURE = 1 << 1,
- SHD_MODULATE = 1 << 2,
- SHD_COLORIZE = 1 << 3,
- SHD_LIGHT_SHADOW = 1 << 4,
- SHD_WORLD_SPACE_PTS = 1 << 5,
- SHD_DEBUG_PRINT = 1 << 6,
- SHD_ADD = 1 << 7,
- SHD_OBJ_TRANSPARENT = 1 << 8,
- SHD_COLOR = 1 << 9,
- SHD_EXP2 = 1 << 10,
- SHD_CAMERA_ALIGNED = 1 << 11,
- SHD_DISTORT = 1 << 12,
- SHD_PREMULTIPLY = 1 << 13,
- SHD_OVERLAY = 1 << 14,
- SHD_EYES = 1 << 15,
- SHD_COLORIZE2 = 1 << 16,
- SHD_HIGHER_QUALITY = 1 << 17,
- SHD_SHADOW = 1 << 18,
- SHD_GLOW = 1 << 19,
- SHD_MASKED = 1 << 20,
- SHD_MASK_UV2 = 1 << 21,
- SHD_CONDITIONAL = 1 << 22,
- SHD_FLATNESS = 1 << 23,
- SHD_DEPTH_BUG_TEST = 1 << 24
-};
-
-// Flags used internally by shaders.
-enum ShaderPrivateFlags {
- PFLAG_USES_POSITION_ATTR = 1,
- PFLAG_USES_UV_ATTR = 1 << 1,
- PFLAG_USES_NORMAL_ATTR = 1 << 2,
- PFLAG_USES_MODEL_WORLD_MATRIX = 1 << 3,
- PFLAG_USES_CAM_POS = 1 << 4,
- PFLAG_USES_SHADOW_PROJECTION_MATRIX = 1 << 5,
- PFLAG_WORLD_SPACE_PTS = 1 << 6,
- PFLAG_USES_ERODE_ATTR = 1 << 7,
- PFLAG_USES_COLOR_ATTR = 1 << 8,
- PFLAG_USES_SIZE_ATTR = 1 << 9,
- PFLAG_USES_DIFFUSE_ATTR = 1 << 10,
- PFLAG_USES_CAM_ORIENT_MATRIX = 1 << 11,
- PFLAG_USES_MODEL_VIEW_MATRIX = 1 << 12,
- PFLAG_USES_UV2_ATTR = 1 << 13
-};
-
-// Look for a gl extension prefixed by "GL_ARB", "GL_EXT", etc
-// returns true if found.
-static auto CheckGLExtension(const char* exts, const char* ext) -> bool {
- char b[128];
- snprintf(b, sizeof(b), "OES_%s", ext);
- if (strstr(exts, b)) return true;
- snprintf(b, sizeof(b), "GL_ARB_%s", ext);
- if (strstr(exts, b)) return true;
- snprintf(b, sizeof(b), "GL_APPLE_%s", ext);
- if (strstr(exts, b)) return true;
- snprintf(b, sizeof(b), "GL_EXT_%s", ext);
- if (strstr(exts, b)) return true;
- snprintf(b, sizeof(b), "GL_NV_%s", ext);
- if (strstr(exts, b)) return true;
- snprintf(b, sizeof(b), "GL_SGIS_%s", ext);
- if (strstr(exts, b)) return true;
- snprintf(b, sizeof(b), "GL_IMG_%s", ext);
- return strstr(exts, b) != nullptr;
+auto RendererGL::GLErrorToString(GLenum err) -> std::string {
+ switch (err) {
+ case GL_NO_ERROR:
+ return "GL_NO_ERROR";
+ case GL_INVALID_ENUM:
+ return "GL_INVALID_ENUM";
+ case GL_INVALID_VALUE:
+ return "GL_INVALID_VALUE";
+ case GL_INVALID_OPERATION:
+ return "GL_INVALID_OPERATION";
+ case GL_OUT_OF_MEMORY:
+ return "GL_OUT_OF_MEMORY";
+ case GL_INVALID_FRAMEBUFFER_OPERATION:
+ return "GL_INVALID_FRAMEBUFFER_OPERATION";
+ default:
+ return std::to_string(err);
+ }
}
-void RendererGL::CheckGLExtensions() {
- DEBUG_CHECK_GL_ERROR;
+// Look for a gl extension prefixed by "GL_ARB", "GL_EXT", etc. Returns true
+// if found.
+static auto CheckGLExtension(const std::vector& exts,
+ const char* ext) -> bool {
+ assert(strlen(ext) < 100);
+ const int variant_count{8};
+ char variants[variant_count][128];
+ int i = 0;
+ snprintf(variants[i], sizeof(variants[i]), "OES_%s", ext);
+ i++;
+ snprintf(variants[i], sizeof(variants[i]), "GL_ARB_%s", ext);
+ i++;
+ snprintf(variants[i], sizeof(variants[i]), "GL_APPLE_%s", ext);
+ i++;
+ snprintf(variants[i], sizeof(variants[i]), "GL_EXT_%s", ext);
+ i++;
+ snprintf(variants[i], sizeof(variants[i]), "GL_NV_%s", ext);
+ i++;
+ snprintf(variants[i], sizeof(variants[i]), "GL_ATI_%s", ext);
+ i++;
+ snprintf(variants[i], sizeof(variants[i]), "GL_SGIS_%s", ext);
+ i++;
+ snprintf(variants[i], sizeof(variants[i]), "GL_IMG_%s", ext);
+ i++;
+ assert(i == variant_count);
+
+ for (auto&& ext : exts) {
+ for (int i = 0; i < variant_count; ++i) {
+ if (variants[i] == ext) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void RendererGL::CheckGLCapabilities_() {
+ BA_DEBUG_CHECK_GL_ERROR;
assert(g_base->InGraphicsThread());
- // const char *version_str = (const char*)glGetString(GL_VERSION);
-
- const char* ex = (const char*)glGetString(GL_EXTENSIONS);
- assert(ex);
- // Log(ex);
-
- // Log(string("GL VERSION: ")+version_str);
draws_shields_funny_set_ = true;
- // const char *renderer = (const char*)glGetString(GL_RENDERER);
- // const char *vendor = (const char*)glGetString(GL_VENDOR);
- // const char *version_str = (const char*)glGetString(GL_VERSION);
- // printf("RENDERER %s\nVENDOR %s\nVERSION %s\n",renderer,vendor,version_str);
-
- // on android, look at the GL version and try to get gl3 funcs to determine if
- // we're running ES3 or not
-#if BA_OSTYPE_ANDROID
-
const char* renderer = (const char*)glGetString(GL_RENDERER);
const char* vendor = (const char*)glGetString(GL_VENDOR);
const char* version_str = (const char*)glGetString(GL_VERSION);
- // Log(string("VER ")+version_str);
-
- bool have_es3;
-
-#if BA_USE_ES3_INCLUDES
- have_es3 = true;
-#else
- have_es3 = (strstr(version_str, "OpenGL ES 3.") && gl3stubInit());
-#endif
-
- // if we require ES3
- if (have_es3) {
- g_running_es3 = true;
- Log(LogLevel::kInfo, std::string("Using OpenGL ES 3 (vendor: ") + vendor
- + ", renderer: " + renderer
- + ", version: " + version_str + ")");
+ const char* basestr;
+ if (gl_is_es()) {
+ basestr = "OpenGL ES";
} else {
-#if !BA_USE_ES3_INCLUDES
- g_running_es3 = false;
- Log(LogLevel::kInfo, std::string("USING OPENGL ES2 (vendor: ") + vendor
- + ", renderer: " + renderer
- + ", version: " + version_str + ")");
-
- // Can still support some stuff like framebuffer-blit with es2 extensions.
- assert(glBlitFramebuffer == nullptr || !first_extension_check_);
- glBlitFramebuffer =
- (decltype(glBlitFramebuffer))eglGetProcAddress("glBlitFramebufferNV");
- assert(glRenderbufferStorageMultisample == nullptr
- || !first_extension_check_);
- glRenderbufferStorageMultisample =
- (decltype(glRenderbufferStorageMultisample))eglGetProcAddress(
- "glRenderbufferStorageMultisampleNV");
-
- assert(glGenVertexArrays == nullptr || !first_extension_check_);
- glGenVertexArrays =
- (decltype(glGenVertexArrays))eglGetProcAddress("glGenVertexArraysOES");
- assert(glDeleteVertexArrays == nullptr || !first_extension_check_);
- glDeleteVertexArrays = (decltype(glDeleteVertexArrays))eglGetProcAddress(
- "glDeleteVertexArraysOES");
- assert(glBindVertexArray == nullptr || !first_extension_check_);
- glBindVertexArray =
- (decltype(glBindVertexArray))eglGetProcAddress("glBindVertexArrayOES");
-
-#endif // BA_USE_ES3_INCLUDES
+ basestr = "OpenGL";
}
- DEBUG_CHECK_GL_ERROR;
+ Log(LogLevel::kInfo, std::string("Using ") + basestr + " (vendor: " + vendor
+ + ", renderer: " + renderer
+ + ", version: " + version_str + ").");
+
+ // Build a vector of extensions. Newer GLs give us extensions as lists
+ // already, but on older ones we may need to break a single string apart
+ // ourself.
+ std::vector extensions;
+ bool used_num_extensions{};
+
+ // On the ES side we still support ES 2 which does not support the modern
+ // GL_NUM_EXTENSIONS route. Though I suppose we could just try it on ES
+ // and do the fallback if we get GL_INVALID_ENUM.
+#if !BA_OPENGL_IS_ES
+ {
+ GLint num_extensions{};
+ glGetError(); // Clear any existing error.
+ glGetIntegerv(GL_NUM_EXTENSIONS, &num_extensions);
+ if (glGetError() == GL_NO_ERROR) {
+ used_num_extensions = true;
+ extensions.reserve(num_extensions);
+ for (int i = 0; i < num_extensions; ++i) {
+ const char* extension = (const char*)glGetStringi(GL_EXTENSIONS, i);
+ BA_PRECONDITION(extension);
+ extensions.push_back(extension);
+ }
+ }
+ }
+#endif
+
+ // Fall back on parsing the single giant string if need be.
+ if (!used_num_extensions) {
+ auto* ex = reinterpret_cast(glGetString(GL_EXTENSIONS));
+ BA_DEBUG_CHECK_GL_ERROR;
+ BA_PRECONDITION_FATAL(ex);
+ std::istringstream iss(ex);
+ extensions = {std::istream_iterator(iss),
+ std::istream_iterator()};
+ }
+
+ // On Android, look at the GL version and try to get gl3 funcs to
+ // determine if we're running ES3 or not.
+#if BA_OSTYPE_ANDROID
+ // bool have_es3;
+ // #if BA_USE_ES3_INCLUDES
+ // have_es3 = true;
+ // #else
+ // have_es3 = (strstr(version_str, "OpenGL ES 3.") && gl3stubInit());
+ // #endif // BA_OSTYPE_ANDROID
+
+ // if (have_es3) {
+ // g_running_es3 = true;
+ // } else {
+ // #if !BA_USE_ES3_INCLUDES
+ // g_running_es3 = false;
+
+ // // Can still support some stuff like framebuffer-blit with es2
+ // extensions. assert(glBlitFramebuffer == nullptr ||
+ // !first_extension_check_); glBlitFramebuffer =
+ // (decltype(glBlitFramebuffer))eglGetProcAddress("glBlitFramebufferNV");
+ // assert(glRenderbufferStorageMultisample == nullptr
+ // || !first_extension_check_);
+ // glRenderbufferStorageMultisample =
+ // (decltype(glRenderbufferStorageMultisample))eglGetProcAddress(
+ // "glRenderbufferStorageMultisampleNV");
+
+ // assert(glGenVertexArrays == nullptr || !first_extension_check_);
+ // glGenVertexArrays =
+ // (decltype(glGenVertexArrays))eglGetProcAddress("glGenVertexArraysOES");
+ // assert(glDeleteVertexArrays == nullptr || !first_extension_check_);
+ // glDeleteVertexArrays =
+ // (decltype(glDeleteVertexArrays))eglGetProcAddress(
+ // "glDeleteVertexArraysOES");
+ // assert(glBindVertexArray == nullptr || !first_extension_check_);
+ // glBindVertexArray =
+ // (decltype(glBindVertexArray))eglGetProcAddress("glBindVertexArrayOES");
+
+ // #endif // BA_USE_ES3_INCLUDES
+ // }
+
+ BA_DEBUG_CHECK_GL_ERROR;
// Flag certain devices as 'speedy' - we use this to enable high/higher
// quality and whatnot (even in cases where ES3 isnt available).
- is_speedy_android_device_ = false;
+ is_speedy_android_device_ = true;
is_extra_speedy_android_device_ = false;
is_adreno_ = (strstr(renderer, "Adreno") != nullptr);
- draws_shields_funny_ = false; // start optimistic.
+ // draws_shields_funny_ = false; // Start optimistic.
- // ali tv box
- if (!strcmp(renderer, "Mali-450 MP")) {
- is_speedy_android_device_ = true; // this is borderline speedy/extra-speedy
- draws_shields_funny_ = true;
- }
+ // Ali tv box.
+ // if (!strcmp(renderer, "Mali-450 MP")) {
+ // is_speedy_android_device_ = true; // this is borderline
+ // speedy/extra-speedy
+ // // draws_shields_funny_ = true;
+ // }
- // firetv, etc.. lets enable MSAA
- if (!strcmp(renderer, "Adreno (TM) 320")) {
- is_recent_adreno_ = true;
- }
+ // Firetv, etc.. lets enable MSAA.
+ // if (!strcmp(renderer, "Adreno (TM) 320")) {
+ // is_recent_adreno_ = true;
+ // }
- // this is right on the borderline, but lets go with extra-speedy i guess
- if (!strcmp(renderer, "Adreno (TM) 330")) {
- is_recent_adreno_ = true;
- is_extra_speedy_android_device_ = true;
- }
+ // This is right on the borderline, but lets go with extra-speedy I guess.
+ // if (!strcmp(renderer, "Adreno (TM) 330")) {
+ // is_recent_adreno_ = true;
+ // is_extra_speedy_android_device_ = true;
+ // }
- // *any* of the 4xx or 5xx series are extra-speedy
- if (strstr(renderer, "Adreno (TM) 4") || strstr(renderer, "Adreno (TM) 5")
- || strstr(renderer, "Adreno (TM) 6")) {
- is_extra_speedy_android_device_ = true;
- is_recent_adreno_ = true;
- }
+ // *any* of the 4xx or 5xx series are extra-speedy.
+ // if (strstr(renderer, "Adreno (TM) 4") || strstr(renderer, "Adreno (TM) 5")
+ // || strstr(renderer, "Adreno (TM) 6")) {
+ // is_extra_speedy_android_device_ = true;
+ // is_recent_adreno_ = true;
+ // }
+
+ // Some speedy malis (Galaxy S6 / Galaxy S7-ish).
+ // if (strstr(renderer, "Mali-T760") || strstr(renderer, "Mali-T860")
+ // || strstr(renderer, "Mali-T880")) {
+ // is_extra_speedy_android_device_ = true;
+ // }
- // some speedy malis (Galaxy S6 / Galaxy S7-ish)
- if (strstr(renderer, "Mali-T760") || strstr(renderer, "Mali-T860")
- || strstr(renderer, "Mali-T880")) {
- is_extra_speedy_android_device_ = true;
- }
// Note 8 is speed-tastic
- if (!strcmp(renderer, "Mali-G71") || !strcmp(renderer, "Mali-G72")) {
- is_extra_speedy_android_device_ = true;
- }
+ // if (!strcmp(renderer, "Mali-G71") || !strcmp(renderer, "Mali-G72")) {
+ // is_extra_speedy_android_device_ = true;
+ // }
- // covers Nexus player
+ // Covers Nexus player.
// HMM Scratch that - this winds up being too slow for phones using this chip.
- if (strstr(renderer, "PowerVR Rogue G6430")) {
- // is_extra_speedy_android_device_ = true;
- }
+ // if (strstr(renderer, "PowerVR Rogue G6430")) {
+ // is_extra_speedy_android_device_ = true;
+ // }
// Figure out if we're a Tegra 4/K1/etc since we do some special stuff on
// those...
- if (!strcmp(renderer, "NVIDIA Tegra")) {
- // tegra 4 won't have ES3 but will have framebuffer_multisample
- if (!g_running_es3 && CheckGLExtension(ex, "framebuffer_multisample")) {
- is_tegra_4_ = true;
- is_speedy_android_device_ = true;
- } else if (g_running_es3) {
- // running ES3 - must be a K1 (for now)
- is_tegra_k1_ = true;
- is_extra_speedy_android_device_ = true;
- } else {
- // looks like Tegra-2 era stuff was just "NVIDIA Tegra" as well...
- }
- }
+ // if (!strcmp(renderer, "NVIDIA Tegra")) {
+ // // tegra 4 won't have ES3 but will have framebuffer_multisample
+ // if (!g_running_es3 && CheckGLExtension(ex, "framebuffer_multisample")) {
+ // is_tegra_4_ = true;
+ // is_speedy_android_device_ = true;
+ // } else if (g_running_es3) {
+ // // running ES3 - must be a K1 (for now)
+ // is_tegra_k1_ = true;
+ // is_extra_speedy_android_device_ = true;
+ // } else {
+ // // looks like Tegra-2 era stuff was just "NVIDIA Tegra" as well...
+ // }
+ // }
// Also store this globally for a few other bits of the app to use..
- g_core->platform->set_is_tegra_k1(is_tegra_k1_);
+ // g_core->platform->set_is_tegra_k1(is_tegra_k1_);
// Extra-speedy implies speedy too..
if (is_extra_speedy_android_device_) {
@@ -370,27 +349,29 @@ void RendererGL::CheckGLExtensions() {
std::list c_types;
assert(g_base->graphics);
- if (CheckGLExtension(ex, "texture_compression_s3tc"))
+ if (CheckGLExtension(extensions, "texture_compression_s3tc"))
c_types.push_back(TextureCompressionType::kS3TC);
- // Limiting pvr support to iOS for the moment.
-#if !BA_OSTYPE_ANDROID
- if (CheckGLExtension(ex, "texture_compression_pvrtc"))
- c_types.push_back(TextureCompressionType::kPVR);
-#endif
+ // Limiting pvr support to iOS for the moment.
+ if (!g_buildconfig.ostype_android()) {
+ if (CheckGLExtension(extensions, "texture_compression_pvrtc"))
+ c_types.push_back(TextureCompressionType::kPVR);
+ }
// All android devices should support etc1.
- if (CheckGLExtension(ex, "compressed_ETC1_RGB8_texture")) {
+ if (CheckGLExtension(extensions, "compressed_ETC1_RGB8_texture")) {
c_types.push_back(TextureCompressionType::kETC1);
} else {
-#if BA_OSTYPE_ANDROID
- Log(LogLevel::kError, "Android device missing ETC1 support");
-#endif
+ if (g_buildconfig.ostype_android()) {
+ Log(LogLevel::kError, "Android device missing ETC1 support");
+ }
}
// ETC2 is required for ES3 support (and OpenGL 4.4 or something once we
- // eventually get there)
- if (g_running_es3) c_types.push_back(TextureCompressionType::kETC2);
+ // eventually get there).
+ if (gl_is_es()) {
+ c_types.push_back(TextureCompressionType::kETC2);
+ }
g_base->graphics_server->SetTextureCompressionTypes(c_types);
@@ -398,169 +379,184 @@ void RendererGL::CheckGLExtensions() {
// depth textures) For now lets also disallow high-quality in some VR
// environments.
- if (CheckGLExtension(ex, "depth_texture")) {
- supports_depth_textures_ = true;
-#if BA_CARDBOARD_BUILD
- g_base->graphics->SetSupportsHighQualityGraphics(false);
-#else // BA_CARDBOARD_BUILD
- g_base->graphics->SetSupportsHighQualityGraphics(true);
-#endif // BA_CARDBOARD_BUILD
- } else {
- supports_depth_textures_ = false;
- g_base->graphics->SetSupportsHighQualityGraphics(false);
- }
+ // Both GL 3.2 and GL ES 3.0 support depth textures.
+ // if (!gl_is_es()) {
+ // supports_depth_textures_ = true;
+ g_base->graphics->SetSupportsHighQualityGraphics(true);
+
+ // } else {
+ // if (CheckGLExtension(extensions, "depth_texture")) {
+ // supports_depth_textures_ = true;
+ // if (g_buildconfig.cardboard_build()) {
+ // g_base->graphics->SetSupportsHighQualityGraphics(false);
+ // } else {
+ // g_base->graphics->SetSupportsHighQualityGraphics(true);
+ // }
+ // } else {
+ // supports_depth_textures_ = false;
+ // g_base->graphics->SetSupportsHighQualityGraphics(false);
+ // }
+ // }
// Store the tex-compression type we support.
- DEBUG_CHECK_GL_ERROR;
+ BA_DEBUG_CHECK_GL_ERROR;
- g_anisotropic_support = CheckGLExtension(ex, "texture_filter_anisotropic");
- if (g_anisotropic_support) {
- glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &g_max_anisotropy);
+ // Anisotropic sampling is still an extension as of both GL 3.2 and ES 3,
+ // so we need to test for it.
+ anisotropic_support_ =
+ CheckGLExtension(extensions, "texture_filter_anisotropic");
+ if (anisotropic_support_) {
+ glGetFloatv(GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &max_anisotropy_);
}
- DEBUG_CHECK_GL_ERROR;
+ BA_DEBUG_CHECK_GL_ERROR;
- // We can run with our without VAOs but they're nice to have.
- g_vao_support =
- (glGenVertexArrays != nullptr && glDeleteVertexArrays != nullptr
- && glBindVertexArray != nullptr
- && (g_running_es3 || CheckGLExtension(ex, "vertex_array_object")));
+ // VAO support is everywhere now.
+ // #if BA_OPENGL_IS_ES
+ // // We can run with our without VAOs but they're nice to have.
+ // g_vao_support =
+ // (glGenVertexArrays != nullptr && glDeleteVertexArrays != nullptr
+ // && glBindVertexArray != nullptr
+ // && (g_running_es3
+ // || CheckGLExtension(extensions, "vertex_array_object")));
+ // #else
+ // g_vao_support = true;
+ // #endif
-#if BA_OSTYPE_IOS_TVOS
- g_blit_framebuffer_support = false;
- g_framebuffer_multisample_support = false;
-#elif BA_OSTYPE_MACOS
- g_blit_framebuffer_support = CheckGLExtension(ex, "framebuffer_blit");
- g_framebuffer_multisample_support = false;
-#else
- g_blit_framebuffer_support =
- (glBlitFramebuffer != nullptr
- && (g_running_es3 || CheckGLExtension(ex, "framebuffer_blit")));
- g_framebuffer_multisample_support =
- (glRenderbufferStorageMultisample != nullptr
- && (g_running_es3 || (CheckGLExtension(ex, "framebuffer_multisample"))));
-#endif
+ // #if !BA_OPENGL_IS_ES
+ // Both of these are standard in GL 3.2 and GL ES 3; hooray!
+ // g_blit_framebuffer_support = true;
+ // g_framebuffer_multisample_support = true;
+ // #else
+ // #if BA_OSTYPE_IOS_TVOS
+ // g_blit_framebuffer_support = false;
+ // g_framebuffer_multisample_support = false;
+ // #elif BA_OSTYPE_MACOS
+ // g_blit_framebuffer_support = CheckGLExtension(extensions,
+ // "framebuffer_blit"); g_framebuffer_multisample_support = false;
+ // #else
+ // g_blit_framebuffer_support =
+ // (glBlitFramebuffer != nullptr
+ // && (g_running_es3 || CheckGLExtension(ex, "framebuffer_blit")));
+ // g_framebuffer_multisample_support =
+ // (glRenderbufferStorageMultisample != nullptr
+ // && (g_running_es3 || (CheckGLExtension(ex,
+ // "framebuffer_multisample"))));
+ // #endif
+ // #endif
-#if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
-
-#if BA_OSTYPE_IOS_TVOS
- g_discard_framebuffer_support = CheckGLExtension(ex, "discard_framebuffer");
-#else
- g_discard_framebuffer_support =
- (glDiscardFramebufferEXT != nullptr
- && CheckGLExtension(ex, "discard_framebuffer"));
-#endif
-
- g_invalidate_framebuffer_support =
- (g_running_es3 && glInvalidateFramebuffer != nullptr);
-#else
- g_discard_framebuffer_support = false;
- g_invalidate_framebuffer_support = false;
-#endif
-
- g_seamless_cube_maps = CheckGLExtension(ex, "seamless_cube_map");
-
-#if BA_OSTYPE_WINDOWS
- // the vmware gl driver breaks horrifically with VAOs turned on
- const char* vendor = (const char*)glGetString(GL_VENDOR);
- if (strstr(vendor, "VMware")) {
- g_vao_support = false;
- }
-#endif
-
-#if BA_OSTYPE_ANDROID
- // VAOs currently break my poor kindle fire hd to the point of rebooting it
- if (!g_running_es3 && !is_tegra_4_) {
- g_vao_support = false;
+ if (gl_is_es()) {
+ // GL ES 3 has glInvalidateFramebuffer as part of the standard.
+ invalidate_framebuffer_support_ = true;
+ } else {
+ // It seems it's standard as of desktop GL 4.3 so we could probably
+ // use it selectively if we wanted.
+ invalidate_framebuffer_support_ = false;
}
- // also they seem to be problematic on zenfone2's gpu.
- if (strstr(renderer, "PowerVR Rogue G6430")) {
- g_vao_support = false;
- }
-#endif
+ // #if BA_OSTYPE_IOS_TVOS || BA_OSTYPE_ANDROID
+ // #if BA_OSTYPE_IOS_TVOS
+ // g_discard_framebuffer_support = CheckGLExtension(ex,
+ // "discard_framebuffer");
+ // #else
+ // g_discard_framebuffer_support =
+ // (glDiscardFramebufferEXT != nullptr
+ // && CheckGLExtension(ex, "discard_framebuffer"));
+ // #endif
+
+ // g_invalidate_framebuffer_support =
+ // (g_running_es3 && glInvalidateFramebuffer != nullptr);
+ // #else
+ // g_discard_framebuffer_support = false;
+ // g_invalidate_framebuffer_support = false;
+ // #endif
+
+ // g_seamless_cube_maps = CheckGLExtension(ex, "seamless_cube_map");
+
+ // #if BA_OSTYPE_WINDOWS
+ // // The vmware gl driver breaks horrifically with VAOs turned on.
+ // const char* vendor = (const char*)glGetString(GL_VENDOR);
+ // if (strstr(vendor, "VMware")) {
+ // g_vao_support = false;
+ // }
+ // #endif
+
+ // #if BA_OSTYPE_ANDROID
+ // // VAOs currently break my poor kindle fire hd to the point of rebooting
+ // it if (!g_running_es3 && !is_tegra_4_) {
+ // g_vao_support = false;
+ // }
+
+ // // also they seem to be problematic on zenfone2's gpu.
+ // if (strstr(renderer, "PowerVR Rogue G6430")) {
+ // g_vao_support = false;
+ // }
+ // #endif
glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS,
- &g_combined_texture_image_unit_count);
+ &combined_texture_image_unit_count_);
// If we're running ES3, ask about our max multisample counts and whether we
// can enable MSAA.
// enable_msaa_ = false; // start pessimistic
- g_msaa_max_samples_rgb565 = g_msaa_max_samples_rgb8 = 0; // start pessimistic
+ msaa_max_samples_rgb565_ = msaa_max_samples_rgb8_ = 0; // start pessimistic
+ // FIXME - NEED TO CHECK DESKTOP GL VERSION TO USE THIS THERE.
#if BA_OSTYPE_ANDROID || BA_RIFT_BUILD
bool check_msaa = false;
#if BA_OSTYPE_ANDROID
- if (g_running_es3) {
- check_msaa = true;
- }
+ // if (g_running_es3) {
+ check_msaa = true;
+ // }
#endif // BA_OSTYPE_ANDROID
+
#if BA_RIFT_BUILD
check_msaa = true;
#endif // BA_RIFT_BUILD
if (check_msaa) {
- if (glGetInternalformativ != nullptr) {
- GLint count;
- glGetInternalformativ(GL_RENDERBUFFER, GL_RGB565, GL_NUM_SAMPLE_COUNTS, 1,
- &count);
- if (count > 0) {
- std::vector samples;
- samples.resize(static_cast(static_cast(count)));
- glGetInternalformativ(GL_RENDERBUFFER, GL_RGB565, GL_SAMPLES, count,
- &samples[0]);
- g_msaa_max_samples_rgb565 = samples[0];
- } else {
- BA_LOG_ONCE(LogLevel::kError, "Got 0 samplecounts for RGB565");
- g_msaa_max_samples_rgb565 = 0;
- }
+ GLint count;
+ glGetInternalformativ(GL_RENDERBUFFER, GL_RGB565, GL_NUM_SAMPLE_COUNTS, 1,
+ &count);
+ if (count > 0) {
+ std::vector samples;
+ samples.resize(static_cast(static_cast(count)));
+ glGetInternalformativ(GL_RENDERBUFFER, GL_RGB565, GL_SAMPLES, count,
+ &samples[0]);
+ msaa_max_samples_rgb565_ = samples[0];
+ } else {
+ BA_LOG_ONCE(LogLevel::kError, "Got 0 samplecounts for RGB565");
+ msaa_max_samples_rgb565_ = 0;
}
- // RGB8 max multisamples
- if (glGetInternalformativ != nullptr) {
- GLint count;
- glGetInternalformativ(GL_RENDERBUFFER, GL_RGB8, GL_NUM_SAMPLE_COUNTS, 1,
- &count);
- if (count > 0) {
- std::vector samples;
- samples.resize(static_cast(count));
- glGetInternalformativ(GL_RENDERBUFFER, GL_RGB8, GL_SAMPLES, count,
- &samples[0]);
- g_msaa_max_samples_rgb8 = samples[0];
- } else {
- BA_LOG_ONCE(LogLevel::kError, "Got 0 samplecounts for RGB8");
- g_msaa_max_samples_rgb8 = 0;
- }
- }
- } else {
- if (is_tegra_4_) {
- // HMM is there a way to query this without ES3?
- g_msaa_max_samples_rgb8 = g_msaa_max_samples_rgb565 = 4;
- }
- }
-#if MSAA_ERROR_TEST
- if (enable_msaa_) {
- ScreenMessage("MSAA ENABLED");
- Log(LogLevel::kInfo, "Ballistica MSAA Test: MSAA ENABLED");
- } else {
- ScreenMessage("MSAA DISABLED");
- Log(LogLevel::kInfo, "Ballistica MSAA Test: MSAA DISABLED");
+ // RGB8 max multisamples.
+ glGetInternalformativ(GL_RENDERBUFFER, GL_RGB8, GL_NUM_SAMPLE_COUNTS, 1,
+ &count);
+ if (count > 0) {
+ std::vector samples;
+ samples.resize(static_cast(count));
+ glGetInternalformativ(GL_RENDERBUFFER, GL_RGB8, GL_SAMPLES, count,
+ &samples[0]);
+ msaa_max_samples_rgb8_ = samples[0];
+ } else {
+ BA_LOG_ONCE(LogLevel::kError, "Got 0 samplecounts for RGB8");
+ msaa_max_samples_rgb8_ = 0;
+ }
}
-#endif // MSAA_ERROR_TEST
#endif // BA_OSTYPE_ANDROID
- DEBUG_CHECK_GL_ERROR;
+ BA_DEBUG_CHECK_GL_ERROR;
first_extension_check_ = false;
}
-auto RendererGL::GetMSAASamplesForFramebuffer(int width, int height) -> int {
+auto RendererGL::GetMSAASamplesForFramebuffer_(int width, int height) -> int {
#if BA_RIFT_BUILD
return 4;
#else
- // we currently aim for 4 up to 800 height and 2 beyond that..
+ // We currently aim for 4 up to 800 height and 2 beyond that.
if (height > 800) {
return 2;
} else {
@@ -569,9 +565,9 @@ auto RendererGL::GetMSAASamplesForFramebuffer(int width, int height) -> int {
#endif
}
-void RendererGL::UpdateMSAAEnabled() {
+void RendererGL::UpdateMSAAEnabled_() {
#if BA_RIFT_BUILD
- if (g_msaa_max_samples_rgb8 > 0) {
+ if (msaa_max_samples_rgb8_ > 0) {
enable_msaa_ = true;
} else {
enable_msaa_ = false;
@@ -581,15 +577,15 @@ void RendererGL::UpdateMSAAEnabled() {
// lets allow full 1080p msaa with newer stuff..
int max_msaa_res = is_tegra_k1_ ? 1200 : 800;
- // to start, see if it looks like we support msaa on paper..
+ // To start, see if it looks like we support msaa on paper.
enable_msaa_ =
((screen_render_target()->physical_height()
<= static_cast(max_msaa_res))
- && (g_msaa_max_samples_rgb8 > 0) && (g_msaa_max_samples_rgb565 > 0));
+ && (msaa_max_samples_rgb8_ > 0) && (msaa_max_samples_rgb565_ > 0));
- // ok, lets be careful here.. msaa blitting/etc seems to be particular in
- // terms of supported formats/etc so let's only enable it on explicitly-tested
- // hardware.
+ // Ok, lets be careful here; msaa blitting/etc seems to be particular in
+ // terms of supported formats/etc so let's only enable it on
+ // explicitly-tested hardware for now.
if (!is_tegra_4_ && !is_tegra_k1_ && !is_recent_adreno_) {
enable_msaa_ = false;
}
@@ -599,7 +595,7 @@ void RendererGL::UpdateMSAAEnabled() {
auto RendererGL::IsMSAAEnabled() const -> bool { return enable_msaa_; }
-static auto GetGLTextureFormat(TextureFormat f) -> GLenum {
+auto RendererGL::GetGLTextureFormat(TextureFormat f) -> GLenum {
switch (f) {
case TextureFormat::kDXT1:
return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
@@ -628,2510 +624,7 @@ static auto GetGLTextureFormat(TextureFormat f) -> GLenum {
}
}
-// a stand-in for vertex-array-objects for use on systems that don't support
-// them directly
-class RendererGL::FakeVertexArrayObject {
- public:
- struct AttrState {
- bool enable;
- GLuint buffer;
- int elem_count;
- GLenum elem_type;
- bool normalized;
- int stride;
- size_t offset;
- };
-
- explicit FakeVertexArrayObject(RendererGL* renderer)
- : renderer_(renderer), elem_buffer_(0) {
- for (auto& attr : attrs_) {
- attr.enable = false;
- }
- }
-
- void Bind() {
- DEBUG_CHECK_GL_ERROR;
-
- // First bind our element buffer.
- assert(elem_buffer_ != 0);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, elem_buffer_);
-
- // Now bind/enable the buffers we use and disable the ones we don't.
- for (GLuint i = 0; i < kVertexAttrCount; i++) {
- if (attrs_[i].enable) {
- renderer_->BindArrayBuffer(attrs_[i].buffer);
- glVertexAttribPointer(i, attrs_[i].elem_count, attrs_[i].elem_type,
- static_cast(attrs_[i].normalized),
- attrs_[i].stride,
- reinterpret_cast(attrs_[i].offset));
- }
- renderer_->SetVertexAttribArrayEnabled(i, attrs_[i].enable);
- }
- DEBUG_CHECK_GL_ERROR;
- }
- void SetElementBuffer(GLuint vbo) { elem_buffer_ = vbo; }
- void SetAttribBuffer(GLuint buffer, VertexAttr attr, int elem_count,
- GLenum elem_type, bool normalized, int stride,
- size_t offset) {
- assert(attr < RendererGL::kVertexAttrCount);
- assert(!attrs_[attr].enable);
- attrs_[attr].enable = true;
- attrs_[attr].buffer = buffer;
- attrs_[attr].elem_count = elem_count;
- attrs_[attr].elem_type = elem_type;
- attrs_[attr].normalized = normalized;
- attrs_[attr].stride = stride;
- attrs_[attr].offset = offset;
- }
-
- AttrState attrs_[RendererGL::kVertexAttrCount]{};
- RendererGL* renderer_{};
- GLuint elem_buffer_{};
-};
-
-class RendererGL::FramebufferObjectGL : public Framebuffer {
- public:
- FramebufferObjectGL(RendererGL* renderer_in, int width_in, int height_in,
- bool linear_interp_in, bool depth_in, bool is_texture_in,
- bool depth_is_texture_in, bool high_quality_in,
- bool msaa_in, bool alpha_in)
- : width_(width_in),
- height_(height_in),
- linear_interp_(linear_interp_in),
- depth_(depth_in),
- is_texture_(is_texture_in),
- depth_is_texture_(depth_is_texture_in),
- renderer_(renderer_in),
- high_quality_(high_quality_in),
- msaa_(msaa_in),
- alpha_(alpha_in) {
- // Desktop stuff is always high-quality
-#if BA_OSTYPE_MACOS || BA_OSTYPE_LINUX || BA_OSTYPE_WINDOWS
- high_quality_ = true;
-#endif
-
- // Things are finally getting to the point where we can default to
- // desktop quality on some mobile stuff.
-#if BA_OSTYPE_ANDROID
- if (renderer_->is_tegra_k1_) {
- high_quality_ = true;
- }
-#endif
-
- Load();
- }
-
- ~FramebufferObjectGL() override { Unload(); }
-
- void Load(bool force_low_quality = false) {
- if (loaded_) return;
- assert(g_base->InGraphicsThread());
- DEBUG_CHECK_GL_ERROR;
- GLenum status;
- DEBUG_CHECK_GL_ERROR;
- glGenFramebuffers(1, &framebuffer_);
- renderer_->BindFramebuffer(framebuffer_);
- DEBUG_CHECK_GL_ERROR;
- bool do_high_quality = high_quality_;
- if (force_low_quality) do_high_quality = false;
- int samples = 0;
- if (msaa_) {
- // Can't multisample with texture buffers currently.
- assert(!is_texture_ && !depth_is_texture_);
-
- int target_samples =
- renderer_->GetMSAASamplesForFramebuffer(width_, height_);
-
- if (do_high_quality) {
- samples = std::min(target_samples, g_msaa_max_samples_rgb8);
- } else {
- samples = std::min(target_samples, g_msaa_max_samples_rgb565);
- }
- }
- if (is_texture_) {
- // attach a texture for the color target
- glGenTextures(1, &texture_);
- renderer_->BindTexture(GL_TEXTURE_2D, texture_);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
- linear_interp_ ? GL_LINEAR : GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
- linear_interp_ ? GL_LINEAR : GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
-
- // On android/ios lets go with 16 bit unless they explicitly request high
- // quality.
-#if BA_OSTYPE_ANDROID || BA_OSTYPE_IOS_TVOS
- GLenum format;
- if (alpha_) {
- format = do_high_quality ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT_4_4_4_4;
- } else {
- format = do_high_quality ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT_5_6_5;
- }
-#else
- GLenum format = GL_UNSIGNED_BYTE;
-#endif
- // if (srgbTest) {
- // glTexImage2D(GL_TEXTURE_2D, 0, alpha_?GL_SRGB8_ALPHA8:GL_SRGB8,
- // _width, _height, 0, alpha_?GL_RGBA:GL_RGB, format, nullptr);
- // } else {
- glTexImage2D(GL_TEXTURE_2D, 0, alpha_ ? GL_RGBA : GL_RGB, width_, height_,
- 0, alpha_ ? GL_RGBA : GL_RGB, format, nullptr);
- // }
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
- GL_TEXTURE_2D, texture_, 0);
- } else {
- // Regular renderbuffer.
- assert(!alpha_); // fixme
-#if BA_OSTYPE_IOS_TVOS
- GLenum format =
- GL_RGB565; // FIXME; need to pull ES3 headers in for GL_RGB8
-#elif BA_OSTYPE_ANDROID
- GLenum format = do_high_quality ? GL_RGB8 : GL_RGB565;
-#else
- GLenum format = GL_RGB8;
-#endif
- glGenRenderbuffers(1, &render_buffer_);
- DEBUG_CHECK_GL_ERROR;
- glBindRenderbuffer(GL_RENDERBUFFER, render_buffer_);
- DEBUG_CHECK_GL_ERROR;
- if (samples > 0) {
-#if BA_OSTYPE_IOS_TVOS
- throw Exception();
-#else
- glRenderbufferStorageMultisample(GL_RENDERBUFFER, samples, format,
- width_, height_);
-#endif
- } else {
- glRenderbufferStorage(GL_RENDERBUFFER, format, width_, height_);
- }
- DEBUG_CHECK_GL_ERROR;
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
- GL_RENDERBUFFER, render_buffer_);
- DEBUG_CHECK_GL_ERROR;
- }
- DEBUG_CHECK_GL_ERROR;
- if (depth_) {
- if (depth_is_texture_) {
- glGenTextures(1, &depth_texture_);
- DEBUG_CHECK_GL_ERROR;
- renderer_->BindTexture(GL_TEXTURE_2D, depth_texture_);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
- DEBUG_CHECK_GL_ERROR;
- // fixme - need to pull in ES3 stuff for iOS to get GL_DEPTH_COMPONENT24
-#if BA_OSTYPE_IOS_TVOS
- glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, width_, height_, 0,
- GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, nullptr);
-#else
- if (do_high_quality) {
-#if BA_OSTYPE_ANDROID
- assert(g_running_es3);
-#endif // BA_OSTYPE_ANDROID
- glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT24, width_, height_,
- 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, nullptr);
- } else {
- glTexImage2D(
- GL_TEXTURE_2D, 0,
- g_running_es3 ? GL_DEPTH_COMPONENT16 : GL_DEPTH_COMPONENT, width_,
- height_, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, nullptr);
- }
-#endif // BA_OSTYPE_IOS_TVOS
-
- DEBUG_CHECK_GL_ERROR;
-
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
- GL_TEXTURE_2D, depth_texture_, 0);
-
- DEBUG_CHECK_GL_ERROR;
- } else {
- // Just use a plain old renderbuffer if we don't need it as a texture
- // (this is more widely supported).
- glGenRenderbuffers(1, &depth_render_buffer_);
- DEBUG_CHECK_GL_ERROR;
- glBindRenderbuffer(GL_RENDERBUFFER, depth_render_buffer_);
- DEBUG_CHECK_GL_ERROR;
-
- if (samples > 0) {
-#if BA_OSTYPE_IOS_TVOS
- throw Exception();
-#else
- // (GL_DEPTH_COMPONENT24 not available in ES2 it looks like)
- bool do24;
-#if BA_OSTYPE_ANDROID
- do24 = (do_high_quality && g_running_es3);
-#else
- do24 = do_high_quality;
-#endif
-
- glRenderbufferStorageMultisample(
- GL_RENDERBUFFER, samples,
- do24 ? GL_DEPTH_COMPONENT24 : GL_DEPTH_COMPONENT16, width_,
- height_);
- // (do_high_quality &&
- // g_running_es3)?GL_DEPTH_COMPONENT24:GL_DEPTH_COMPONENT16, _width,
- // _height);
-#endif
- } else {
- // FIXME - need to pull in es3 headers to get GL_DEPTH_COMPONENT24 on
- // iOS
-#if BA_OSTYPE_IOS_TVOS
- GLenum format = GL_DEPTH_COMPONENT16;
-#else
- // (GL_DEPTH_COMPONENT24 not available in ES2 it looks like)
- GLenum format = (do_high_quality && g_running_es3)
- ? GL_DEPTH_COMPONENT24
- : GL_DEPTH_COMPONENT16;
-#endif
-
- glRenderbufferStorage(GL_RENDERBUFFER, format, width_, height_);
- }
-
- DEBUG_CHECK_GL_ERROR;
- glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
- GL_RENDERBUFFER, depth_render_buffer_);
- DEBUG_CHECK_GL_ERROR;
- }
- }
-
- status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
-
- if (status != GL_FRAMEBUFFER_COMPLETE) {
- const char* version = (const char*)glGetString(GL_VERSION);
- const char* vendor = (const char*)glGetString(GL_VENDOR);
- const char* renderer = (const char*)glGetString(GL_RENDERER);
- throw Exception(
- "Framebuffer setup failed for " + std::to_string(width_) + " by "
- + std::to_string(height_) + " fb with depth " + std::to_string(depth_)
- + " asTex " + std::to_string(depth_is_texture_) + " gl-version "
- + version + " vendor " + vendor + " renderer " + renderer);
- }
- // GLint enc;
- // glGetFramebufferAttachmentParameteriv(GL_FRAMEBUFFER,
- // GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, &enc); if
- // (enc == GL_SRGB) {
- // Log(LogLevel::kInfo, "GOT SRGB!!!!!!!!!!!");
- // } else if (enc == GL_LINEAR) {
- // Log(LogLevel::kInfo, "GOT LINEAR...");
- // } else {
- // Log(LogLevel::kInfo, "GOT OTHER..");
- // }
- loaded_ = true;
- }
-
- void Unload() {
- assert(g_base->InGraphicsThread());
- if (!loaded_) return;
-
- // If our textures are currently bound as anything, clear that out.
- // (otherwise a new texture with that same ID won't be bindable)
- for (int& i : renderer_->bound_textures_2d_) {
- if (i == texture_) { // NOLINT(bugprone-branch-clone)
- i = -1;
- } else if (depth_ && (i == depth_texture_)) {
- i = -1;
- }
- }
-
- if (!g_base->graphics_server->renderer_context_lost()) {
- // Tear down the FBO and texture attachment
- if (is_texture_) {
- glDeleteTextures(1, &texture_);
- } else {
- glDeleteRenderbuffers(1, &render_buffer_);
- }
- if (depth_) {
- if (depth_is_texture_) {
- glDeleteTextures(1, &depth_texture_);
- } else {
- glDeleteRenderbuffers(1, &depth_render_buffer_);
- }
- DEBUG_CHECK_GL_ERROR;
- }
-
- // If this one is current, make sure we re-bind next time.
- // (otherwise we might prevent a new framebuffer with a recycled id from
- // binding)
- if (renderer_->active_framebuffer_ == framebuffer_) {
- renderer_->active_framebuffer_ = -1;
- }
- glDeleteFramebuffers(1, &framebuffer_);
- DEBUG_CHECK_GL_ERROR;
- }
- loaded_ = false;
- }
-
- void Bind() {
- assert(g_base->InGraphicsThread());
- renderer_->BindFramebuffer(framebuffer_);
- // if (time(nullptr)%2 == 0) {
- // glDisable(GL_FRAMEBUFFER_SRGB);
- // }
- }
-
- auto texture() const -> GLuint {
- assert(is_texture_);
- return texture_;
- }
-
- auto depth_texture() const -> GLuint {
- assert(depth_ && depth_is_texture_);
- return depth_texture_;
- }
-
- auto width() const -> int { return width_; }
- auto height() const -> int { return height_; }
- auto id() const -> GLuint { return framebuffer_; }
-
- private:
- RendererGL* renderer_{};
- bool depth_{};
- bool is_texture_{};
- bool depth_is_texture_{};
- bool high_quality_{};
- bool msaa_{};
- bool alpha_{};
- bool linear_interp_{};
- bool loaded_{};
- int width_{}, height_{};
- GLuint framebuffer_{}, texture_{}, depth_texture_{}, render_buffer_{},
- depth_render_buffer_{};
-}; // FramebufferObject
-
-// Base class for fragment/vertex shaders.
-class RendererGL::ShaderGL : public Object {
- public:
- auto GetDefaultOwnerThread() const -> EventLoopID override {
- return EventLoopID::kMain;
- }
-
- ShaderGL(GLenum type_in, const std::string& src_in) : type_(type_in) {
- assert(g_base->InGraphicsThread());
- DEBUG_CHECK_GL_ERROR;
- assert(type_ == GL_FRAGMENT_SHADER || type_ == GL_VERTEX_SHADER);
- shader_ = glCreateShader(type_);
- DEBUG_CHECK_GL_ERROR;
- BA_PRECONDITION(shader_);
- const char* s = src_in.c_str();
- glShaderSource(shader_, 1, &s, nullptr);
- glCompileShader(shader_);
- GLint compile_status;
- glGetShaderiv(shader_, GL_COMPILE_STATUS, &compile_status);
- if (compile_status == GL_FALSE) {
- const char* version = (const char*)glGetString(GL_VERSION);
- const char* vendor = (const char*)glGetString(GL_VENDOR);
- const char* renderer = (const char*)glGetString(GL_RENDERER);
- // Let's not crash here. We have a better chance of calling home this way
- // and theres a chance the game will still be playable.
- Log(LogLevel::kError,
- std::string("Compile failed for ") + GetTypeName()
- + " shader:\n------------SOURCE BEGIN-------------\n" + src_in
- + "\n-----------SOURCE END-------------\n" + GetInfo()
- + "\nrenderer: " + renderer + "\nvendor: " + vendor
- + "\nversion:" + version);
- } else {
- assert(compile_status == GL_TRUE);
- std::string info = GetInfo();
- if (!info.empty()
- && (strstr(info.c_str(), "error:") || strstr(info.c_str(), "warning:")
- || strstr(info.c_str(), "Error:")
- || strstr(info.c_str(), "Warning:"))) {
- const char* version = (const char*)glGetString(GL_VERSION);
- const char* vendor = (const char*)glGetString(GL_VENDOR);
- const char* renderer = (const char*)glGetString(GL_RENDERER);
- Log(LogLevel::kError,
- std::string("WARNING: info returned for ") + GetTypeName()
- + " shader:\n------------SOURCE BEGIN-------------\n" + src_in
- + "\n-----------SOURCE END-------------\n" + info
- + "\nrenderer: " + renderer + "\nvendor: " + vendor
- + "\nversion:" + version);
- }
- }
- DEBUG_CHECK_GL_ERROR;
- }
- ~ShaderGL() override {
- assert(g_base->InGraphicsThread());
- if (!g_base->graphics_server->renderer_context_lost()) {
- glDeleteShader(shader_);
- DEBUG_CHECK_GL_ERROR;
- }
- }
- auto shader() const -> GLuint { return shader_; }
-
- private:
- auto GetTypeName() const -> const char* {
- if (type_ == GL_VERTEX_SHADER) {
- return "vertex";
- } else {
- return "fragment";
- }
- }
- auto GetInfo() -> std::string {
- static char log[1024];
- GLsizei log_size;
- glGetShaderInfoLog(shader_, sizeof(log), &log_size, log);
- return log;
- }
- std::string name_;
- GLuint shader_{};
- GLenum type_{};
- BA_DISALLOW_CLASS_COPIES(ShaderGL);
-}; // ShaderGL
-
-//-----------------------------------------------------------------
-
-class RendererGL::FragmentShaderGL : public RendererGL::ShaderGL {
- public:
- explicit FragmentShaderGL(const std::string& src_in)
- : ShaderGL(GL_FRAGMENT_SHADER, src_in) {}
-};
-
-//-------------------------------------------------------------------
-
-class RendererGL::VertexShaderGL : public RendererGL::ShaderGL {
- public:
- explicit VertexShaderGL(const std::string& src_in)
- : ShaderGL(GL_VERTEX_SHADER, src_in) {}
-};
-
-//-------------------------------------------------------------------
-
-class RendererGL::ProgramGL {
- public:
- ProgramGL(RendererGL* renderer,
- const Object::Ref& vertex_shader_in,
- const Object::Ref& fragment_shader_in,
- std::string name, int pflags)
- : fragment_shader_(fragment_shader_in),
- vertex_shader_(vertex_shader_in),
- renderer_(renderer),
- pflags_(pflags),
- name_(std::move(name)) {
- assert(g_base->InGraphicsThread());
- DEBUG_CHECK_GL_ERROR;
- program_ = glCreateProgram();
- BA_PRECONDITION(program_);
- glAttachShader(program_, fragment_shader_->shader());
- glAttachShader(program_, vertex_shader_->shader());
- assert(pflags_ & PFLAG_USES_POSITION_ATTR);
- if (pflags_ & PFLAG_USES_POSITION_ATTR)
- glBindAttribLocation(program_, kVertexAttrPosition, "position");
- if (pflags_ & PFLAG_USES_UV_ATTR)
- glBindAttribLocation(program_, kVertexAttrUV, "uv");
- if (pflags_ & PFLAG_USES_NORMAL_ATTR)
- glBindAttribLocation(program_, kVertexAttrNormal, "normal");
- if (pflags_ & PFLAG_USES_ERODE_ATTR)
- glBindAttribLocation(program_, kVertexAttrErode, "erode");
- if (pflags_ & PFLAG_USES_COLOR_ATTR)
- glBindAttribLocation(program_, kVertexAttrColor, "color");
- if (pflags_ & PFLAG_USES_SIZE_ATTR)
- glBindAttribLocation(program_, kVertexAttrSize, "size");
- if (pflags_ & PFLAG_USES_DIFFUSE_ATTR)
- glBindAttribLocation(program_, kVertexAttrDiffuse, "diffuse");
- if (pflags_ & PFLAG_USES_UV2_ATTR)
- glBindAttribLocation(program_, kVertexAttrUV2, "uv2");
- glLinkProgram(program_);
- GLint linkStatus;
- glGetProgramiv(program_, GL_LINK_STATUS, &linkStatus);
- if (linkStatus == GL_FALSE) {
- Log(LogLevel::kError,
- "Link failed for program '" + name_ + "':\n" + GetInfo());
- } else {
- assert(linkStatus == GL_TRUE);
-
- std::string info = GetInfo();
- if (!info.empty()
- && (strstr(info.c_str(), "error:") || strstr(info.c_str(), "warning:")
- || strstr(info.c_str(), "Error:")
- || strstr(info.c_str(), "Warning:"))) {
- Log(LogLevel::kError, "WARNING: program using frag shader '" + name_
- + "' returned info:\n" + info);
- }
- }
-
- // go ahead and bind ourself so child classes can config uniforms and
- // whatnot
- Bind();
- mvp_uniform_ = glGetUniformLocation(program_, "modelViewProjectionMatrix");
- assert(mvp_uniform_ != -1);
- if (pflags_ & PFLAG_USES_MODEL_WORLD_MATRIX) {
- model_world_matrix_uniform_ =
- glGetUniformLocation(program_, "modelWorldMatrix");
- assert(model_world_matrix_uniform_ != -1);
- }
- if (pflags_ & PFLAG_USES_MODEL_VIEW_MATRIX) {
- model_view_matrix_uniform_ =
- glGetUniformLocation(program_, "modelViewMatrix");
- assert(model_view_matrix_uniform_ != -1);
- }
- if (pflags_ & PFLAG_USES_CAM_POS) {
- cam_pos_uniform_ = glGetUniformLocation(program_, "camPos");
- assert(cam_pos_uniform_ != -1);
- }
- if (pflags_ & PFLAG_USES_CAM_ORIENT_MATRIX) {
- cam_orient_matrix_uniform_ =
- glGetUniformLocation(program_, "camOrientMatrix");
- assert(cam_orient_matrix_uniform_ != -1);
- }
- if (pflags_ & PFLAG_USES_SHADOW_PROJECTION_MATRIX) {
- light_shadow_projection_matrix_uniform_ =
- glGetUniformLocation(program_, "lightShadowProjectionMatrix");
- assert(light_shadow_projection_matrix_uniform_ != -1);
- }
- }
-
- virtual ~ProgramGL() {
- assert(g_base->InGraphicsThread());
- if (!g_base->graphics_server->renderer_context_lost()) {
- glDetachShader(program_, fragment_shader_->shader());
- glDetachShader(program_, vertex_shader_->shader());
- glDeleteProgram(program_);
- DEBUG_CHECK_GL_ERROR;
- }
- }
- auto IsBound() const -> bool {
- return (renderer()->GetActiveProgram() == this);
- }
-
- auto program() const -> GLuint { return program_; }
-
- void Bind() { renderer_->UseProgram(this); }
-
- auto name() const -> const std::string& { return name_; }
-
- // should grab matrices from the renderer
- // or whatever else it needs in prep for drawing
- void PrepareToDraw() {
- DEBUG_CHECK_GL_ERROR;
-
- assert(IsBound());
-
- // update matrices as necessary...
-
- uint32_t mvpState =
- g_base->graphics_server->GetModelViewProjectionMatrixState();
- if (mvpState != mvp_state_) {
- mvp_state_ = mvpState;
- glUniformMatrix4fv(
- mvp_uniform_, 1, 0,
- g_base->graphics_server->GetModelViewProjectionMatrix().m);
- }
- DEBUG_CHECK_GL_ERROR;
-
- if (pflags_ & PFLAG_USES_MODEL_WORLD_MATRIX) {
- assert(!(pflags_
- & PFLAG_WORLD_SPACE_PTS)); // with world space points this would
- // be identity; don't waste time.
- uint32_t state = g_base->graphics_server->GetModelWorldMatrixState();
- if (state != model_world_matrix_state_) {
- model_world_matrix_state_ = state;
- glUniformMatrix4fv(model_world_matrix_uniform_, 1, 0,
- g_base->graphics_server->GetModelWorldMatrix().m);
- }
- }
- DEBUG_CHECK_GL_ERROR;
-
- if (pflags_ & PFLAG_USES_MODEL_VIEW_MATRIX) {
- assert(!(pflags_
- & PFLAG_WORLD_SPACE_PTS)); // with world space points this would
- // be identity; don't waste time.
- // there's no state for just modelview but this works
- uint32_t state =
- g_base->graphics_server->GetModelViewProjectionMatrixState();
- if (state != model_view_matrix_state_) {
- model_view_matrix_state_ = state;
- glUniformMatrix4fv(model_view_matrix_uniform_, 1, 0,
- g_base->graphics_server->model_view_matrix().m);
- }
- }
- DEBUG_CHECK_GL_ERROR;
-
- if (pflags_ & PFLAG_USES_CAM_POS) {
- uint32_t state = g_base->graphics_server->cam_pos_state();
- if (state != cam_pos_state_) {
- cam_pos_state_ = state;
- const Vector3f& p(g_base->graphics_server->cam_pos());
- glUniform4f(cam_pos_uniform_, p.x, p.y, p.z, 1.0f);
- }
- }
- DEBUG_CHECK_GL_ERROR;
-
- if (pflags_ & PFLAG_USES_CAM_ORIENT_MATRIX) {
- uint32_t state = g_base->graphics_server->GetCamOrientMatrixState();
- if (state != cam_orient_matrix_state_) {
- cam_orient_matrix_state_ = state;
- glUniformMatrix4fv(cam_orient_matrix_uniform_, 1, 0,
- g_base->graphics_server->GetCamOrientMatrix().m);
- }
- }
- DEBUG_CHECK_GL_ERROR;
-
- if (pflags_ & PFLAG_USES_SHADOW_PROJECTION_MATRIX) {
- uint32_t state =
- g_base->graphics_server->light_shadow_projection_matrix_state();
- if (state != light_shadow_projection_matrix_state_) {
- light_shadow_projection_matrix_state_ = state;
- glUniformMatrix4fv(
- light_shadow_projection_matrix_uniform_, 1, 0,
- g_base->graphics_server->light_shadow_projection_matrix().m);
- }
- }
- DEBUG_CHECK_GL_ERROR;
- }
-
- protected:
- void SetTextureUnit(const char* tex_name, int unit) {
- assert(IsBound());
- int c = glGetUniformLocation(program_, tex_name);
- if (c == -1) {
-#if !MSAA_ERROR_TEST
- Log(LogLevel::kError, "ShaderGL: " + name_
- + ": Can't set texture unit for texture '"
- + tex_name + "'");
- DEBUG_CHECK_GL_ERROR;
-#endif
- } else {
- glUniform1i(c, unit);
- }
- }
-
- auto GetInfo() -> std::string {
- static char log[1024];
- GLsizei log_size;
- glGetProgramInfoLog(program_, sizeof(log), &log_size, log);
- return log;
- }
-
- auto renderer() const -> RendererGL* { return renderer_; }
-
- private:
- RendererGL* renderer_{};
- Object::Ref fragment_shader_;
- Object::Ref vertex_shader_;
- std::string name_;
- GLuint program_{};
- int pflags_{};
- uint32_t mvp_state_{};
- GLint mvp_uniform_{};
- GLint model_world_matrix_uniform_{};
- GLint model_view_matrix_uniform_{};
- GLint light_shadow_projection_matrix_uniform_{};
- uint32_t light_shadow_projection_matrix_state_{};
- uint32_t model_world_matrix_state_{};
- uint32_t model_view_matrix_state_{};
- GLint cam_pos_uniform_{};
- uint32_t cam_pos_state_{};
- GLint cam_orient_matrix_uniform_{};
- GLuint cam_orient_matrix_state_{};
- BA_DISALLOW_CLASS_COPIES(ProgramGL);
-}; // ProgramGL
-
-class RendererGL::SimpleProgramGL : public RendererGL::ProgramGL {
- public:
- enum TextureUnit {
- kColorTexUnit,
- kColorizeTexUnit,
- kMaskTexUnit,
- kMaskUV2TexUnit,
- kBlurTexUnit
- };
-
- SimpleProgramGL(RendererGL* renderer, int flags)
- : RendererGL::ProgramGL(
- renderer, Object::New(GetVertexCode(flags)),
- Object::New(GetFragmentCode(flags)), GetName(flags),
- GetPFlags(flags)),
- flags_(flags) {
- if (flags & SHD_TEXTURE) {
- SetTextureUnit("colorTex", kColorTexUnit);
- }
- if (flags & SHD_COLORIZE) {
- SetTextureUnit("colorizeTex", kColorizeTexUnit);
- colorize_color_location_ =
- glGetUniformLocation(program(), "colorizeColor");
- assert(colorize_color_location_ != -1);
- }
- if (flags & SHD_COLORIZE2) {
- colorize2_color_location_ =
- glGetUniformLocation(program(), "colorize2Color");
- assert(colorize2_color_location_ != -1);
- }
- if ((!(flags & SHD_TEXTURE)) || (flags & SHD_MODULATE)) {
- color_location_ = glGetUniformLocation(program(), "color");
- assert(color_location_ != -1);
- }
- if (flags & SHD_SHADOW) {
- shadow_params_location_ = glGetUniformLocation(program(), "shadowParams");
- assert(shadow_params_location_ != -1);
- }
- if (flags & SHD_GLOW) {
- glow_params_location_ = glGetUniformLocation(program(), "glowParams");
- assert(glow_params_location_ != -1);
- }
- if (flags & SHD_FLATNESS) {
- flatness_location = glGetUniformLocation(program(), "flatness");
- assert(flatness_location != -1);
- }
- if (flags & SHD_MASKED) {
- SetTextureUnit("maskTex", kMaskTexUnit);
- }
- if (flags & SHD_MASK_UV2) {
- SetTextureUnit("maskUV2Tex", kMaskUV2TexUnit);
- }
- }
- void SetColorTexture(const TextureAsset* t) {
- assert(flags_ & SHD_TEXTURE);
- assert(IsBound());
- renderer()->BindTexture(GL_TEXTURE_2D, t, kColorTexUnit);
- }
- void SetColorTexture(GLuint t) {
- renderer()->BindTexture(GL_TEXTURE_2D, t, kColorTexUnit);
- }
- void SetColor(float r, float g, float b, float a = 1.0f) {
- assert((flags_ & SHD_MODULATE) || !(flags_ & SHD_TEXTURE));
- assert(IsBound());
- if (r != r_ || g != g_ || b != b_ || a != a_) {
- r_ = r;
- g_ = g;
- b_ = b;
- a_ = a;
- glUniform4f(color_location_, r_, g_, b_, a_);
- }
- }
- void SetColorizeColor(float r, float g, float b, float a = 1.0f) {
- assert(flags_ & SHD_COLORIZE);
- assert(IsBound());
- if (r != colorize_r_ || g != colorize_g_ || b != colorize_b_
- || a != colorize_a_) {
- colorize_r_ = r;
- colorize_g_ = g;
- colorize_b_ = b;
- colorize_a_ = a;
- glUniform4f(colorize_color_location_, colorize_r_, colorize_g_,
- colorize_b_, colorize_a_);
- }
- }
- void SetShadow(float shadow_offset_x, float shadow_offset_y,
- float shadow_blur, float shadow_density) {
- assert(flags_ & SHD_SHADOW);
- assert(IsBound());
- if (shadow_offset_x != shadow_offset_x_
- || shadow_offset_y != shadow_offset_y_ || shadow_blur != shadow_blur_
- || shadow_density != shadow_density_) {
- shadow_offset_x_ = shadow_offset_x;
- shadow_offset_y_ = shadow_offset_y;
- shadow_blur_ = shadow_blur;
- shadow_density_ = shadow_density;
- glUniform4f(shadow_params_location_, shadow_offset_x_, shadow_offset_y_,
- shadow_blur_, shadow_density_ * 0.4f);
- }
- }
- void setGlow(float glow_amount, float glow_blur) {
- assert(flags_ & SHD_GLOW);
- assert(IsBound());
- if (glow_amount != glow_amount_ || glow_blur != glow_blur_) {
- glow_amount_ = glow_amount;
- glow_blur_ = glow_blur;
- glUniform2f(glow_params_location_, glow_amount_, glow_blur_);
- }
- }
- void SetFlatness(float flatness) {
- assert(flags_ & SHD_FLATNESS);
- assert(IsBound());
- if (flatness != flatness_) {
- flatness_ = flatness;
- glUniform1f(flatness_location, flatness_);
- }
- }
- void SetColorize2Color(float r, float g, float b, float a = 1.0f) {
- assert(flags_ & SHD_COLORIZE2);
- assert(IsBound());
- if (r != colorize2_r_ || g != colorize2_g_ || b != colorize2_b_
- || a != colorize2_a_) {
- colorize2_r_ = r;
- colorize2_g_ = g;
- colorize2_b_ = b;
- colorize2_a_ = a;
- glUniform4f(colorize2_color_location_, colorize2_r_, colorize2_g_,
- colorize2_b_, colorize2_a_);
- }
- }
- void SetColorizeTexture(const TextureAsset* t) {
- assert(flags_ & SHD_COLORIZE);
- renderer()->BindTexture(GL_TEXTURE_2D, t, kColorizeTexUnit);
- }
- void SetMaskTexture(const TextureAsset* t) {
- assert(flags_ & SHD_MASKED);
- renderer()->BindTexture(GL_TEXTURE_2D, t, kMaskTexUnit);
- }
- void SetMaskUV2Texture(const TextureAsset* t) {
- assert(flags_ & SHD_MASK_UV2);
- renderer()->BindTexture(GL_TEXTURE_2D, t, kMaskUV2TexUnit);
- }
-
- private:
- auto GetName(int flags) -> std::string {
- return "SimpleProgramGL texture:"
- + std::to_string((flags & SHD_TEXTURE) != 0)
- + " modulate:" + std::to_string((flags & SHD_MODULATE) != 0)
- + " colorize:" + std::to_string((flags & SHD_COLORIZE) != 0)
- + " colorize2:" + std::to_string((flags & SHD_COLORIZE2) != 0)
- + " premultiply:" + std::to_string((flags & SHD_PREMULTIPLY) != 0)
- + " shadow:" + std::to_string((flags & SHD_SHADOW) != 0)
- + " glow:" + std::to_string((flags & SHD_GLOW) != 0) + " masked:"
- + std::to_string((flags & SHD_MASKED) != 0) + " maskedUV2:"
- + std::to_string((flags & SHD_MASK_UV2) != 0) + " depthBugTest:"
- + std::to_string((flags & SHD_DEPTH_BUG_TEST) != 0)
- + " flatness:" + std::to_string((flags & SHD_MASK_UV2) != 0);
- }
- auto GetPFlags(int flags) -> int {
- int pflags = PFLAG_USES_POSITION_ATTR;
- if (flags & SHD_TEXTURE) pflags |= PFLAG_USES_UV_ATTR;
- if (flags & SHD_MASK_UV2) pflags |= PFLAG_USES_UV2_ATTR;
- return pflags;
- }
- auto GetVertexCode(int flags) -> std::string {
- std::string s;
- s = "uniform mat4 modelViewProjectionMatrix;\n"
- "attribute vec4 position;\n";
- if ((flags & SHD_TEXTURE) || (flags & SHD_COLORIZE)
- || (flags & SHD_COLORIZE2))
- s += "attribute vec2 uv;\n"
- "varying vec2 vUV;\n";
- if (flags & SHD_MASK_UV2)
- s += "attribute vec2 uv2;\n"
- "varying vec2 vUV2;\n";
- if (flags & SHD_SHADOW)
- s += "varying vec2 vUVShadow;\n"
- "varying vec2 vUVShadow2;\n"
- "varying vec2 vUVShadow3;\n"
- "uniform " LOWP "vec4 shadowParams;\n";
- s += "void main() {\n";
- if (flags & SHD_TEXTURE) s += " vUV = uv;\n";
- if (flags & SHD_MASK_UV2) s += " vUV2 = uv2;\n";
- if (flags & SHD_SHADOW)
- s += " vUVShadow = uv+0.4*vec2(shadowParams.x,shadowParams.y);\n";
- if (flags & SHD_SHADOW)
- s += " vUVShadow2 = uv+0.8*vec2(shadowParams.x,shadowParams.y);\n";
- if (flags & SHD_SHADOW)
- s += " vUVShadow3 = uv+1.3*vec2(shadowParams.x,shadowParams.y);\n";
- s += " gl_Position = modelViewProjectionMatrix*position;\n"
- "}";
-
- if (flags & SHD_DEBUG_PRINT)
- Log(LogLevel::kInfo,
- "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s);
- return s;
- }
- auto GetFragmentCode(int flags) -> std::string {
- std::string s;
- if (flags & SHD_TEXTURE) s += "uniform " LOWP "sampler2D colorTex;\n";
- if ((flags & SHD_COLORIZE))
- s += "uniform " LOWP
- "sampler2D colorizeTex;\n"
- "uniform " LOWP "vec4 colorizeColor;\n";
- if ((flags & SHD_COLORIZE2)) s += "uniform " LOWP "vec4 colorize2Color;\n";
- if ((flags & SHD_TEXTURE) || (flags & SHD_COLORIZE)
- || (flags & SHD_COLORIZE2))
- s += "varying " LOWP "vec2 vUV;\n";
- if (flags & SHD_MASK_UV2) s += "varying " LOWP "vec2 vUV2;\n";
- if (flags & SHD_FLATNESS) s += "uniform " LOWP "float flatness;\n";
- if (flags & SHD_SHADOW) {
- s += "varying " LOWP
- "vec2 vUVShadow;\n"
- "varying " LOWP
- "vec2 vUVShadow2;\n"
- "varying " LOWP
- "vec2 vUVShadow3;\n"
- "uniform " LOWP "vec4 shadowParams;\n";
- }
- if (flags & SHD_GLOW) {
- s += "uniform " LOWP "vec2 glowParams;\n";
- }
- if ((flags & SHD_MODULATE) || (!(flags & SHD_TEXTURE)))
- s += "uniform " LOWP "vec4 color;\n";
- if (flags & SHD_MASKED) s += "uniform " LOWP "sampler2D maskTex;\n";
- if (flags & SHD_MASK_UV2) s += "uniform " LOWP "sampler2D maskUV2Tex;\n";
- s += "void main() {\n";
- if (!(flags & SHD_TEXTURE)) {
- s += " gl_FragColor = color;\n";
- } else {
- std::string blurArg;
- if (flags & SHD_GLOW) {
- s += " " LOWP
- "vec4 cVal = texture2D(colorTex,vUV,glowParams.g);\n"
- " gl_FragColor = vec4(color.rgb * cVal.rgb * cVal.a * "
- "glowParams.r,0.0)"; // we premultiply this.
- if (flags & SHD_MASK_UV2) s += " * vec4(texture2D(maskUV2Tex,vUV2).a)";
- s += ";\n";
- } else {
- if ((flags & SHD_COLORIZE) || (flags & SHD_COLORIZE2))
- s += " " LOWP
- "vec4 colorizeVal = texture2D(colorizeTex,vUV);\n"; // TEMP TEST
- if (flags & SHD_COLORIZE)
- s += " " LOWP "float colorizeA = colorizeVal.r;\n";
- if (flags & SHD_COLORIZE2)
- s += " " LOWP "float colorizeB = colorizeVal.g;\n";
- if (flags & SHD_MASKED)
- s += " " MEDIUMP "vec4 mask = texture2D(maskTex,vUV);";
-
- if (flags & SHD_MODULATE) {
- if (flags & SHD_FLATNESS) {
- s += " " LOWP
- "vec4 rawTexColor = texture2D(colorTex,vUV);\n"
- " gl_FragColor = color * "
- "vec4(mix(rawTexColor.rgb,vec3(1.0),flatness),rawTexColor.a)";
- } else {
- s += " gl_FragColor = color * texture2D(colorTex,vUV)";
- }
- } else {
- s += " gl_FragColor = texture2D(colorTex,vUV)";
- }
-
- if (flags & SHD_COLORIZE)
- s += " * (vec4(1.0-colorizeA)+colorizeColor*colorizeA)";
- if (flags & SHD_COLORIZE2)
- s += " * (vec4(1.0-colorizeB)+colorize2Color*colorizeB)";
- if (flags & SHD_MASKED)
- s += " * vec4(vec3(mask.r),mask.a) + "
- "vec4(vec3(mask.g)*colorizeColor.rgb+vec3(mask.b),0.0)";
- s += ";\n";
-
- if (flags & SHD_SHADOW) {
- s += " " LOWP
- "float shadowA = (texture2D(colorTex,vUVShadow).a + "
- "texture2D(colorTex,vUVShadow2,1.0).a + "
- "texture2D(colorTex,vUVShadow3,2.0).a) * shadowParams.a";
-
- if (flags & SHD_MASK_UV2) s += " * texture2D(maskUV2Tex,vUV2).a";
- s += ";\n";
- s += " gl_FragColor = "
- "vec4(gl_FragColor.rgb*gl_FragColor.a,gl_FragColor.a) + "
- "(1.0-gl_FragColor.a) * vec4(0,0,0,shadowA);\n";
- s += " gl_FragColor = "
- "vec4(gl_FragColor.rgb/"
- "max(0.001,gl_FragColor.a),gl_FragColor.a);\n";
- }
- }
- if (flags & SHD_DEPTH_BUG_TEST)
- s += " gl_FragColor = vec4(abs(gl_FragCoord.z-gl_FragColor.r));\n";
- if (flags & SHD_PREMULTIPLY)
- s += " gl_FragColor = vec4(gl_FragColor.rgb * "
- "gl_FragColor.a,gl_FragColor.a);";
- }
- s += "}";
-
- if (flags & SHD_DEBUG_PRINT)
- Log(LogLevel::kInfo,
- "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s);
- return s;
- }
- float r_{}, g_{}, b_{}, a_{};
- float colorize_r_{}, colorize_g_{}, colorize_b_{}, colorize_a_{};
- float colorize2_r_{}, colorize2_g_{}, colorize2_b_{}, colorize2_a_{};
- float shadow_offset_x_{}, shadow_offset_y_{}, shadow_blur_{},
- shadow_density_{};
- float glow_amount_{}, glow_blur_{};
- float flatness_{};
- GLint color_location_{};
- GLint colorize_color_location_{};
- GLint colorize2_color_location_{};
- GLint shadow_params_location_{};
- GLint glow_params_location_{};
- GLint flatness_location{};
- int flags_{};
-}; // SimpleProgramGL
-
-class RendererGL::ObjectProgramGL : public RendererGL::ProgramGL {
- public:
- enum TextureUnit {
- kColorTexUnit,
- kReflectionTexUnit,
- kVignetteTexUnit,
- kLightShadowTexUnit,
- kColorizeTexUnit
- };
-
- ObjectProgramGL(RendererGL* renderer, int flags)
- : RendererGL::ProgramGL(
- renderer, Object::New(GetVertexCode(flags)),
- Object::New(GetFragmentCode(flags)), GetName(flags),
- GetPFlags(flags)),
- flags_(flags),
- r_(0),
- g_(0),
- b_(0),
- a_(0),
- colorize_r_(0),
- colorize_g_(0),
- colorize_b_(0),
- colorize_a_(0),
- colorize2_r_(0),
- colorize2_g_(0),
- colorize2_b_(0),
- colorize2_a_(0),
- add_r_(0),
- add_g_(0),
- add_b_(0),
- r_mult_r_(0),
- r_mult_g_(0),
- r_mult_b_(0),
- r_mult_a_(0) {
- SetTextureUnit("colorTex", kColorTexUnit);
- SetTextureUnit("vignetteTex", kVignetteTexUnit);
- color_location_ = glGetUniformLocation(program(), "color");
- assert(color_location_ != -1);
- if (flags & SHD_REFLECTION) {
- SetTextureUnit("reflectionTex", kReflectionTexUnit);
- reflect_mult_location_ = glGetUniformLocation(program(), "reflectMult");
- assert(reflect_mult_location_ != -1);
- }
- if (flags & SHD_LIGHT_SHADOW) {
- SetTextureUnit("lightShadowTex", kLightShadowTexUnit);
- }
- if (flags & SHD_ADD) {
- color_add_location_ = glGetUniformLocation(program(), "colorAdd");
- assert(color_add_location_ != -1);
- }
- if (flags & SHD_COLORIZE) {
- SetTextureUnit("colorizeTex", kColorizeTexUnit);
- colorize_color_location_ =
- glGetUniformLocation(program(), "colorizeColor");
- assert(colorize_color_location_ != -1);
- }
- if (flags & SHD_COLORIZE2) {
- colorize2_color_location_ =
- glGetUniformLocation(program(), "colorize2Color");
- assert(colorize2_color_location_ != -1);
- }
- }
- void SetColorTexture(const TextureAsset* t) {
- renderer()->BindTexture(GL_TEXTURE_2D, t, kColorTexUnit);
- }
- void SetReflectionTexture(const TextureAsset* t) {
- assert(flags_ & SHD_REFLECTION);
- renderer()->BindTexture(GL_TEXTURE_CUBE_MAP, t, kReflectionTexUnit);
- }
- void SetColor(float r, float g, float b, float a = 1.0f) {
- assert(IsBound());
- // include tint..
- if (r * renderer()->tint().x != r_ || g * renderer()->tint().y != g_
- || b * renderer()->tint().z != b_ || a != a_) {
- r_ = r * renderer()->tint().x;
- g_ = g * renderer()->tint().y;
- b_ = b * renderer()->tint().z;
- a_ = a;
- glUniform4f(color_location_, r_, g_, b_, a_);
- }
- }
- void SetAddColor(float r, float g, float b) {
- assert(IsBound());
- if (r != add_r_ || g != add_g_ || b != add_b_) {
- add_r_ = r;
- add_g_ = g;
- add_b_ = b;
- glUniform4f(color_add_location_, add_r_, add_g_, add_b_, 0.0f);
- }
- }
- void SetReflectionMult(float r, float g, float b, float a = 0.0f) {
- assert(IsBound());
- // include tint and ambient color...
- auto renderer = this->renderer();
- float rFin = r * renderer->tint().x * renderer->ambient_color().x;
- float gFin = g * renderer->tint().y * renderer->ambient_color().y;
- float bFin = b * renderer->tint().z * renderer->ambient_color().z;
- if (rFin != r_mult_r_ || gFin != r_mult_g_ || bFin != r_mult_b_
- || a != r_mult_a_) {
- r_mult_r_ = rFin;
- r_mult_g_ = gFin;
- r_mult_b_ = bFin;
- r_mult_a_ = a;
- assert(flags_ & SHD_REFLECTION);
- glUniform4f(reflect_mult_location_, r_mult_r_, r_mult_g_, r_mult_b_,
- r_mult_a_);
- }
- }
- void SetVignetteTexture(GLuint t) {
- renderer()->BindTexture(GL_TEXTURE_2D, t, kVignetteTexUnit);
- }
- void SetLightShadowTexture(GLuint t) {
- renderer()->BindTexture(GL_TEXTURE_2D, t, kLightShadowTexUnit);
- }
-
- void SetColorizeColor(float r, float g, float b, float a = 1.0f) {
- assert(flags_ & SHD_COLORIZE);
- assert(IsBound());
- if (r != colorize_r_ || g != colorize_g_ || b != colorize_b_
- || a != colorize_a_) {
- colorize_r_ = r;
- colorize_g_ = g;
- colorize_b_ = b;
- colorize_a_ = a;
- glUniform4f(colorize_color_location_, colorize_r_, colorize_g_,
- colorize_b_, colorize_a_);
- }
- }
- void SetColorize2Color(float r, float g, float b, float a = 1.0f) {
- assert(flags_ & SHD_COLORIZE2);
- assert(IsBound());
- if (r != colorize2_r_ || g != colorize2_g_ || b != colorize2_b_
- || a != colorize2_a_) {
- colorize2_r_ = r;
- colorize2_g_ = g;
- colorize2_b_ = b;
- colorize2_a_ = a;
- glUniform4f(colorize2_color_location_, colorize2_r_, colorize2_g_,
- colorize2_b_, colorize2_a_);
- }
- }
- void SetColorizeTexture(const TextureAsset* t) {
- assert(flags_ & SHD_COLORIZE);
- renderer()->BindTexture(GL_TEXTURE_2D, t, kColorizeTexUnit);
- }
-
- private:
- auto GetName(int flags) -> std::string {
- return std::string("ObjectProgramGL")
- + " reflect:" + std::to_string((flags & SHD_REFLECTION) != 0)
- + " lightShadow:" + std::to_string((flags & SHD_LIGHT_SHADOW) != 0)
- + " add:" + std::to_string((flags & SHD_ADD) != 0) + " colorize:"
- + std::to_string((flags & SHD_COLORIZE) != 0) + " colorize2:"
- + std::to_string((flags & SHD_COLORIZE2) != 0) + " transparent:"
- + std::to_string((flags & SHD_OBJ_TRANSPARENT) != 0) + " worldSpace:"
- + std::to_string((flags & SHD_WORLD_SPACE_PTS) != 0);
- }
- auto GetPFlags(int flags) -> int {
- int pflags = PFLAG_USES_POSITION_ATTR | PFLAG_USES_UV_ATTR;
- if (flags & SHD_REFLECTION)
- pflags |= (PFLAG_USES_NORMAL_ATTR | PFLAG_USES_CAM_POS);
- if (((flags & SHD_REFLECTION) || (flags & SHD_LIGHT_SHADOW))
- && !(flags & SHD_WORLD_SPACE_PTS))
- pflags |= PFLAG_USES_MODEL_WORLD_MATRIX;
- if (flags & SHD_LIGHT_SHADOW) pflags |= PFLAG_USES_SHADOW_PROJECTION_MATRIX;
- if (flags & SHD_WORLD_SPACE_PTS) pflags |= PFLAG_WORLD_SPACE_PTS;
- return pflags;
- }
- auto GetVertexCode(int flags) -> std::string {
- std::string s;
- s = "uniform mat4 modelViewProjectionMatrix;\n"
- "uniform vec4 camPos;\n"
- "attribute vec4 position;\n"
- "attribute " LOWP
- "vec2 uv;\n"
- "varying " LOWP
- "vec2 vUV;\n"
- "varying " MEDIUMP "vec4 vScreenCoord;\n";
- if ((flags & SHD_REFLECTION) || (flags & SHD_LIGHT_SHADOW))
- s += "uniform mat4 modelWorldMatrix;\n";
- if (flags & SHD_REFLECTION)
- s += "attribute " MEDIUMP
- "vec3 normal;\n"
- "varying " MEDIUMP "vec3 vReflect;\n";
- if (flags & SHD_LIGHT_SHADOW)
- s += "uniform mat4 lightShadowProjectionMatrix;\n"
- "varying " MEDIUMP "vec4 vLightShadowUV;\n";
- s +=
- "void main() {\n"
- " vUV = uv;\n"
- " gl_Position = modelViewProjectionMatrix*position;\n"
- " vScreenCoord = vec4(gl_Position.xy/gl_Position.w,gl_Position.zw);\n"
- " vScreenCoord.xy += vec2(1.0);\n"
- " vScreenCoord.xy *= vec2(0.5*vScreenCoord.w);\n";
- if (((flags & SHD_LIGHT_SHADOW) || (flags & SHD_REFLECTION))
- && !(flags & SHD_WORLD_SPACE_PTS)) {
- s += " vec4 worldPos = modelWorldMatrix*position;\n";
- }
- if (flags & SHD_LIGHT_SHADOW) {
- if (flags & SHD_WORLD_SPACE_PTS)
- s += " vLightShadowUV = (lightShadowProjectionMatrix*position);\n";
- else
- s += " vLightShadowUV = (lightShadowProjectionMatrix*worldPos);\n";
- }
- if (flags & SHD_REFLECTION) {
- if (flags & SHD_WORLD_SPACE_PTS)
- s += " vReflect = reflect(vec3(position - camPos),normal);\n";
- else
- s += " vReflect = reflect(vec3(worldPos - "
- "camPos),normalize(vec3(modelWorldMatrix * vec4(normal,0.0))));\n";
- }
- s += "}";
- if (flags & SHD_DEBUG_PRINT)
- Log(LogLevel::kInfo,
- "\nVertex code for shader '" + GetName(flags) + "':\n\n" + s);
- return s;
- }
- auto GetFragmentCode(int flags) -> std::string {
- std::string s;
- s = "uniform " LOWP
- "sampler2D colorTex;\n"
- "uniform " LOWP
- "sampler2D vignetteTex;\n"
- "uniform " LOWP
- "vec4 color;\n"
- "varying " LOWP
- "vec2 vUV;\n"
- "varying " MEDIUMP "vec4 vScreenCoord;\n";
- if (flags & SHD_ADD) s += "uniform " LOWP "vec4 colorAdd;\n";
- if (flags & SHD_REFLECTION)
- s += "uniform " LOWP
- "samplerCube reflectionTex;\n"
- "varying " MEDIUMP
- "vec3 vReflect;\n"
- "uniform " LOWP "vec4 reflectMult;\n";
- if (flags & SHD_COLORIZE)
- s += "uniform " LOWP
- "sampler2D colorizeTex;\n"
- "uniform " LOWP "vec4 colorizeColor;\n";
- if (flags & SHD_COLORIZE2) s += "uniform " LOWP "vec4 colorize2Color;\n";
- if (flags & SHD_LIGHT_SHADOW)
- s += "uniform " LOWP
- "sampler2D lightShadowTex;\n"
- "varying " MEDIUMP "vec4 vLightShadowUV;\n";
- s += "void main() {\n";
- if (flags & SHD_LIGHT_SHADOW)
- s +=
- " " LOWP
- "vec4 lightShadVal = texture2DProj(lightShadowTex,vLightShadowUV);\n";
- if ((flags & SHD_COLORIZE) || (flags & SHD_COLORIZE2))
- s += " " LOWP "vec4 colorizeVal = texture2D(colorizeTex,vUV);\n";
- if (flags & SHD_COLORIZE)
- s += " " LOWP "float colorizeA = colorizeVal.r;\n";
- if (flags & SHD_COLORIZE2)
- s += " " LOWP "float colorizeB = colorizeVal.g;\n";
- s += " gl_FragColor = (color*texture2D(colorTex,vUV)";
- if (flags & SHD_COLORIZE)
- s += " * (vec4(1.0-colorizeA)+colorizeColor*colorizeA)";
- if (flags & SHD_COLORIZE2)
- s += " * (vec4(1.0-colorizeB)+colorize2Color*colorizeB)";
- s += ")";
-
- // add in lights/shadows
- if (flags & SHD_LIGHT_SHADOW) {
- if (flags & SHD_OBJ_TRANSPARENT)
- s += " * vec4((2.0*lightShadVal).rgb,1) + "
- "vec4((lightShadVal-0.5).rgb,0)";
- else
- s += " * (2.0*lightShadVal) + (lightShadVal-0.5)";
- }
-
- // add glow and reflection
- if (flags & SHD_REFLECTION)
- s += " + (reflectMult*textureCube(reflectionTex,vReflect))";
- if (flags & SHD_ADD) s += " + colorAdd";
-
- // subtract vignette
- s += " - vec4(texture2DProj(vignetteTex,vScreenCoord).rgb,0)";
-
- s += ";\n";
- // s += "gl_FragColor = 0.999 * texture2DProj(vignetteTex,vScreenCoord) +
- // 0.01 * gl_FragColor;";
-
- s += "}";
-
- if (flags & SHD_DEBUG_PRINT)
- Log(LogLevel::kInfo,
- "\nFragment code for shader '" + GetName(flags) + "':\n\n" + s);
- return s;
- }
- float r_, g_, b_, a_;
- float colorize_r_, colorize_g_, colorize_b_, colorize_a_;
- float colorize2_r_, colorize2_g_, colorize2_b_, colorize2_a_;
- float add_r_, add_g_, add_b_;
- float r_mult_r_, r_mult_g_, r_mult_b_, r_mult_a_;
- GLint color_location_;
- GLint colorize_color_location_;
- GLint colorize2_color_location_;
- GLint color_add_location_;
- GLint reflect_mult_location_;
- int flags_;
-}; // ObjectProgramGL
-
-class RendererGL::SmokeProgramGL : public RendererGL::ProgramGL {
- public:
- enum TextureUnit { kColorTexUnit, kDepthTexUnit, kBlurTexUnit };
-
- SmokeProgramGL(RendererGL* renderer, int flags)
- : RendererGL::ProgramGL(
- renderer, Object::New(GetVertexCode(flags)),
- Object::New