├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── build.gradle ├── pom.xml └── src └── main ├── java └── com │ └── badlogic │ └── gdx │ ├── checksum │ ├── CRC32.java │ ├── SHA1.java │ └── SHA1FileTable.java │ ├── concurrent │ ├── AsyncTask.java │ ├── AsyncTaskExecutor.java │ ├── AsyncTaskJob.java │ ├── ReentrantLock.java │ ├── ReentrantReadWriteLock.java │ ├── ThreadLocalArray.java │ └── ThreadLocalInstance.java │ ├── files │ ├── FileStreamConsumer.java │ ├── FileStreamReader.java │ ├── FileUtils.java │ └── TextFileUtils.java │ ├── function │ ├── BiConsumer.java │ ├── BiFunction.java │ ├── BiPredicate.java │ ├── BooleanSupplier.java │ ├── Consumer.java │ ├── Function.java │ ├── Functions.java │ ├── IntConsumer.java │ ├── IntFunction.java │ ├── IntObjConsumer.java │ ├── IntPredicate.java │ ├── Iterables.java │ ├── Predicate.java │ ├── Supplier.java │ ├── ThrowableBiFunction.java │ ├── ThrowableConsumer.java │ ├── ThrowableFunction.java │ ├── ThrowableSupplier.java │ ├── ToBooleanFunction.java │ ├── ToByteBiFunction.java │ └── ToIntFunction.java │ ├── graphics │ ├── GL33Ext.java │ ├── TextureUtils.java │ ├── g2d │ │ ├── PixmapAtlas.java │ │ ├── PixmapRegion.java │ │ ├── PixmapTransform.java │ │ └── PixmapUtils.java │ └── glutils │ │ ├── GLBufferObject.java │ │ ├── GLTextureUtils.java │ │ ├── IndexBufferObjectExt.java │ │ ├── MultiTargetFrameBuffer.java │ │ ├── ShaderProgramExt.java │ │ ├── VertexArrayObject.java │ │ ├── VertexAttributeArray.java │ │ └── VertexBufferObjectExt.java │ ├── json │ ├── AnnotatedJson.java │ ├── AnnotatedJsonObject.java │ ├── AnnotatedJsonSerializer.java │ ├── JsonArraySerializer.java │ ├── JsonFloatSerializer.java │ ├── JsonMapSerializer.java │ └── annotations │ │ ├── JsonArray.java │ │ ├── JsonMap.java │ │ ├── JsonSerializable.java │ │ └── JsonSerialize.java │ ├── lang │ ├── Box.java │ ├── ClassFinder.java │ ├── ClassFinderCache.java │ ├── ClassUtils.java │ └── FourCC.java │ ├── profiler │ ├── Profiler.java │ ├── RemoteryLwjgl.java │ └── Sample.java │ ├── random │ ├── RandomNumberGenerator.java │ ├── RandomNumbers.java │ └── XoRoShiRo128Plus.java │ ├── scenes │ └── scene2d │ │ └── ui │ │ ├── ActorLayout.java │ │ ├── BaseLayout.java │ │ ├── ButtonLayout.java │ │ ├── ContainerLayout.java │ │ ├── GroupLayout.java │ │ ├── ImageLayout.java │ │ ├── LabelLayout.java │ │ ├── LayoutGroup.java │ │ ├── StageLayout.java │ │ ├── StageLayoutAdapter.java │ │ ├── StageLayoutListener.java │ │ ├── TextButtonLayout.java │ │ └── TreeTools.java │ └── utils │ ├── ArrayUtils.java │ ├── AutoDisposable.java │ ├── AutoDispose.java │ ├── AutoDisposer.java │ ├── GdxRuntimeExceptionWithLog.java │ ├── GdxSnippets.java │ ├── GdxSnippetsNativesLoader.java │ ├── Host.java │ ├── InputUtils.java │ ├── LimitedPool.java │ ├── Memory.java │ └── VirtualScreenViewport.java └── native ├── .gitignore └── jni ├── CMakeLists.txt ├── fips ├── fips.cmd ├── fips.yml ├── flextgl ├── flextGL.c ├── flextGL.h ├── flextgl.sh └── flextgl_profile.txt ├── generated └── gdx-snippets-jni.yml └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ IDEA 2 | /.idea/ 3 | *.iml 4 | 5 | # Maven 6 | /target/ 7 | *.properties 8 | 9 | # Gradle 10 | /build/ 11 | /out/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "deps/flextGL"] 2 | path = deps/flextGL 3 | url = https://github.com/code-disaster/flextGL.git -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2018 Daniel Ludwig 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libGDX snippets 2 | 3 | A collection of code snippets for the lazy [libGDX](https://github.com/libgdx/libgdx) user. 4 | 5 | ## About 6 | 7 | #### Aim 8 | 9 | This library is not a framework, nor an engine. It is just a collection of Java classes which might prove worth (re-)using in a **libGDX desktop project**. 10 | 11 | #### Update policy 12 | 13 | You may notice that commits are done at rather strange intervals. This code is extended and improved in conjunction with our current game development project at [Robotality](http://robotality.com). I found Git submodules very cumbersome to work with, esp. when working in a team with artists and designers. That said, the *main* branch of this library is actually embedded in our game project, using this GitHub repository as a backup/mirror. 14 | 15 | This means that changes done are pretty much instantly tested in our production environment, but commits to this public repository are often lagging behind, and are done in batches. 16 | 17 | > This also means that there can be *API breaking* changes at any time! 18 | 19 | ## Outline 20 | 21 | - Aimed for desktop projects, so the code won't run on HTML5 and mobile. 22 | - Written/compiled with JDK 8, and uses Java 1.8 language features. Be advised you may encounter heavy (ab)use of lambdas. 23 | - Invades the ```com.badlogic.gdx.*``` namespace. 24 | - Embeds a small native library for some utility functions. 25 | 26 | ## Highlights 27 | 28 | > 2018-Nov-28: I removed the native interfaces to __GLSL Optimizer__ and __Remotery__. GLSL Optimizer didn't have any noticeable performance impact for our use cases, and isn't applicable to many of our shaders (GLSL > 150). The Remotery wrapper has been replaced by the more complete and well-maintained LWJGL Remotery bindings. 29 | 30 | - [AnnotatedJson](https://github.com/code-disaster/libgdx-snippets/blob/master/src/main/java/com/badlogic/gdx/json/AnnotatedJson.java): custom JSON serializer, based on libGDX' Json classes, which allows annotation-driven serialization of object hierarchies. 31 | - [AutoDisposer](https://github.com/code-disaster/libgdx-snippets/blob/master/src/main/java/com/badlogic/gdx/utils/AutoDisposer.java): annotation-driven, semi-automatic disposal of disposable object hierarchies. 32 | - [GL33Ext](https://github.com/code-disaster/libgdx-snippets/blob/master/src/main/java/com/badlogic/gdx/graphics/GL33Ext.java): native interface to [flextGL](https://github.com/code-disaster/flextGL.git) to expose desktop OpenGL functions not made available by libGDX/LWJGL. 33 | - [MultiTargetFrameBuffer](https://github.com/code-disaster/libgdx-snippets/blob/master/src/main/java/com/badlogic/gdx/graphics/glutils/MultiTargetFrameBuffer.java): custom GLFrameBuffer implementation for creating multi-render targets, e.g. usable for deferred rendering (G-buffers). 34 | 35 | ## Usage 36 | 37 | To use the native interfaces, you just need to add a call to ```GdxSnippetsNativesLoader.load()```, for example in your create() function. 38 | 39 | ```java 40 | public class MyGdxGameAdapter extends ApplicationAdapter { 41 | @Override 42 | public void create() { 43 | GdxSnippetsNativesLoader.load( 44 | true /* load native library */, 45 | true /* setup GL function bindings using flextGL *); 46 | } 47 | } 48 | ``` 49 | 50 | ## Building from source 51 | 52 | ### Java package 53 | 54 | This is a Maven project. Just use ```mvn package``` or ```mvn install``` to create a snapshot. 55 | 56 | ### Native libraries 57 | 58 | This library uses the [fips](http://floooh.github.io/fips/) cmake build wrapper to compile the native source code. Please read the [list of requirements](http://floooh.github.io/fips/getstarted.html) to run fips (in short: Python 2.7.9, cmake 2.8.11+, and an appropriate C++ compiler environment). In addition, Maven and Java are required for the [fips-jni](https://github.com/code-disaster/fips-jni) module. 59 | 60 | The steps below should work on every target system. You only need to specify a different build target. 61 | 62 | **The root folder for the native code is located in ```[libgdx-snippets]/src/main/native/jni/```.** 63 | 64 | ```shell 65 | # navigate to the native source folder 66 | > cd [libgdx-snippets]/src/main/native/jni/ 67 | ``` 68 | 69 | **To install fips plus dependencies (one-time):** 70 | 71 | ```shell 72 | # this will install fips 73 | > ./fips 74 | 75 | # fetch dependencies 76 | > ./fips fetch 77 | 78 | # setup fips-jni 79 | > ./fips jni setup 80 | ``` 81 | 82 | **To select the build target (one-time):** 83 | 84 | ```shell 85 | # [optional] list all configs known to fips 86 | > ./fips list configs 87 | 88 | # e.g. for Windows 64 bit, using VS2017 89 | > ./fips set config win64-vs2017-release 90 | 91 | # or, for OS X, using XCode 92 | > ./fips set config osx-xcode-release 93 | ``` 94 | 95 | **To compile the native library:** 96 | 97 | ```shell 98 | # [optional] clean output 99 | > ./fips clean all 100 | 101 | # generate JNI code and build the library 102 | > ./fips build 103 | ``` 104 | 105 | > Note: *fips-jni* uses *gdx-jnigen* to generate native code from C++ embedded in Java source files. *Gdx-jnigen* parses both ```.java``` and ```.class``` files. This means that, to compile the native library successfully, it is required to compile the Java code first, e.g. via ```mvn compile``` from the root folder, or from inside your favourite IDE. 106 | 107 | **To copy the compiled runtime library:** 108 | 109 | ```shell 110 | # e.g. for Windows 64 bit 111 | > mvn install -Pwin64-vs2017 112 | 113 | # or, for OS X 114 | > mvn install -Posx-xcode 115 | ``` 116 | 117 | > Note: this copies the compiled runtime library to ```[libgdx-snippets]/src/main/resources```. 118 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "java" 2 | 3 | dependencies { 4 | if (file("${rootProject.projectDir}/libs/gdx.jar").exists()) { 5 | // note: for internal use, dependency to local libGDX version 6 | compile files("${rootProject.projectDir}/libs/gdx.jar") 7 | } else { 8 | compile "com.badlogicgames.gdx:gdx:${gdxVersion}" 9 | } 10 | compile "org.slf4j:slf4j-api:${slf4jVersion}" 11 | compile "org.lwjgl:lwjgl:$lwjglVersion" 12 | compile "org.lwjgl:lwjgl-remotery:$lwjglVersion" 13 | } 14 | 15 | sourceCompatibility = 1.8 16 | [compileJava, compileTestJava]*.options*.encoding = 'UTF-8' 17 | 18 | sourceSets.main.java.srcDirs = [ "src/main/java" ] 19 | sourceSets.main.resources.srcDirs = [ "src/main/resources" ] 20 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.code-disaster 6 | gdx-snippets 7 | 1.0.0-SNAPSHOT 8 | 9 | jar 10 | libGDX lazy snippets 11 | 12 | 13 | 1.8 14 | 1.8 15 | UTF-8 16 | UTF-8 17 | 18 | 19 | 20 | 21 | com.badlogicgames.gdx 22 | gdx 23 | ${gdx.version} 24 | provided 25 | 26 | 27 | org.lwjgl 28 | lwjgl 29 | ${lwjgl.version} 30 | provided 31 | 32 | 33 | org.lwjgl 34 | lwjgl-remotery 35 | ${lwjgl.version} 36 | provided 37 | 38 | 39 | org.slf4j 40 | slf4j-api 41 | 1.7.13 42 | 43 | 44 | 45 | 46 | 47 | 48 | maven-compiler-plugin 49 | 3.1 50 | 51 | ${maven.compiler.source} 52 | ${maven.compiler.target} 53 | 54 | 55 | 56 | org.apache.maven.plugins 57 | maven-source-plugin 58 | 2.2.1 59 | 60 | 61 | attach-sources 62 | 63 | jar-no-fork 64 | 65 | 66 | 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-javadoc-plugin 71 | 2.9.1 72 | 73 | 74 | attach-javadocs 75 | 76 | jar 77 | 78 | 79 | -Xdoclint:none 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/checksum/CRC32.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.checksum; 2 | 3 | import java.nio.charset.Charset; 4 | 5 | public class CRC32 { 6 | 7 | /** 8 | * Lookup table generated with polynomial = reflect(0x04c11db7). 9 | */ 10 | private static final int table[] = { 11 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 12 | 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 13 | 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 14 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 15 | 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 16 | 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 17 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 18 | 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 19 | 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 20 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 21 | 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 22 | 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 23 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 24 | 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 25 | 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 26 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 27 | 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 28 | 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 29 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 30 | 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 31 | 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 32 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 33 | 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 34 | 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 35 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 36 | 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 37 | 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 38 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 39 | 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 40 | 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 41 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 42 | 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; 43 | 44 | private int value; 45 | 46 | public static final CRC32 Zero = CRC32.valueOf(0); 47 | 48 | private static final Charset charset = Charset.defaultCharset(); 49 | 50 | private CRC32(int value) { 51 | this.value = value; 52 | } 53 | 54 | public boolean equals(int value) { 55 | return this.value == value; 56 | } 57 | 58 | public boolean equals(CRC32 checksum) { 59 | return this.value == checksum.value; 60 | } 61 | 62 | @Override 63 | public boolean equals(Object object) { 64 | return (object instanceof CRC32) && equals((CRC32) object); 65 | } 66 | 67 | @Override 68 | public int hashCode() { 69 | return value; 70 | } 71 | 72 | public byte[] getHash() { 73 | return new byte[] { 74 | (byte) (value >>> 24), 75 | (byte) (value >>> 16), 76 | (byte) (value >>> 8), 77 | (byte) value }; 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | return String.format("%08x", value); 83 | } 84 | 85 | public static CRC32 reset(CRC32 checksum) { 86 | checksum.value = 0; 87 | return checksum; 88 | } 89 | 90 | public static boolean isNullOrZero(CRC32 checksum) { 91 | return checksum == null || checksum.value == 0; 92 | } 93 | 94 | public static CRC32 valueOf(int value) { 95 | return new CRC32(value); 96 | } 97 | 98 | public static CRC32 calculate(byte[] buffer) { 99 | return CRC32.update(new CRC32(0), buffer, 0, buffer.length); 100 | } 101 | 102 | public static CRC32 calculate(CRC32 checksum, byte[] buffer) { 103 | checksum.value = 0; 104 | return CRC32.update(checksum, buffer, 0, buffer.length); 105 | } 106 | 107 | public static CRC32 calculateString(String text) { 108 | return CRC32.calculate(text.getBytes(charset)); 109 | } 110 | 111 | public static CRC32 calculateString(CRC32 checksum, String text) { 112 | return CRC32.calculate(checksum, text.getBytes(charset)); 113 | } 114 | 115 | public static CRC32 update(CRC32 checksum, byte buffer[], int offset, int length) { 116 | checksum.value = ~checksum.value; 117 | 118 | for (int i = offset; i < offset + length; ++i) { 119 | int idx = buffer[i] & 0xff; 120 | checksum.value = (checksum.value >>> 8) ^ table[idx ^ (checksum.value & 0x000000ff)]; 121 | } 122 | 123 | checksum.value = ~checksum.value; 124 | 125 | return checksum; 126 | } 127 | 128 | public static CRC32 updateString(CRC32 checksum, String text) { 129 | return update(checksum, text.getBytes(charset), 0, text.length()); 130 | } 131 | 132 | public static int compare(CRC32 x, CRC32 y) { 133 | return Integer.compare(x.value, y.value); 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/checksum/SHA1.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.checksum; 2 | 3 | import com.badlogic.gdx.utils.GdxRuntimeException; 4 | import com.badlogic.gdx.utils.StringBuilder; 5 | 6 | import java.security.*; 7 | import java.util.Arrays; 8 | import java.util.PrimitiveIterator; 9 | 10 | public class SHA1 { 11 | 12 | private final byte[] value = new byte[20]; 13 | 14 | public static final SHA1 Zero = new SHA1(); 15 | 16 | private SHA1() { 17 | 18 | } 19 | 20 | private SHA1(byte[] value) { 21 | System.arraycopy(value, 0, this.value, 0, 20); 22 | } 23 | 24 | private SHA1(CharSequence value) { 25 | 26 | if (value.length() != 40) { 27 | throw new GdxRuntimeException(""); 28 | } 29 | 30 | PrimitiveIterator.OfInt chars = value.chars().iterator(); 31 | 32 | for (int i = 0; i < 20; i++) { 33 | 34 | int upper = chars.nextInt(); 35 | upper = (upper >= 'a') ? (upper - 'a' + 10) : (upper - '0'); 36 | int lower = chars.nextInt(); 37 | lower = (lower >= 'a') ? (lower - 'a' + 10) : (lower - '0'); 38 | 39 | int v = (upper << 4) | lower; 40 | 41 | this.value[i] = (byte) v; 42 | } 43 | } 44 | 45 | @Override 46 | public int hashCode() { 47 | return Arrays.hashCode(value); 48 | } 49 | 50 | public boolean equals(SHA1 value) { 51 | return Arrays.equals(this.value, value.value); 52 | } 53 | 54 | @Override 55 | public boolean equals(Object object) { 56 | return (object instanceof SHA1) && equals((SHA1) object); 57 | } 58 | 59 | public static boolean isNullOrZero(SHA1 value) { 60 | return value == null || Zero.equals(value); 61 | } 62 | 63 | public static SHA1 valueOf(byte[] value) { 64 | return new SHA1(value); 65 | } 66 | 67 | public static SHA1 valueOf(CharSequence value) { 68 | return new SHA1(value); 69 | } 70 | 71 | public static SHA1 create() { 72 | SHA1 value = new SHA1(); 73 | reset(value); 74 | return value; 75 | } 76 | 77 | public static SHA1 reset(SHA1 value) { 78 | 79 | if (!algorithm.isCurrent(null)) { 80 | throw new GdxRuntimeException(""); 81 | } 82 | 83 | algorithm.makeCurrent(value); 84 | algorithm.digest.get().reset(); 85 | 86 | return value; 87 | } 88 | 89 | public static SHA1 update(SHA1 value, byte[] buffer, int offset, int length) { 90 | 91 | if (!algorithm.isCurrent(value)) { 92 | throw new GdxRuntimeException(""); 93 | } 94 | 95 | algorithm.digest.get().update(buffer, offset, length); 96 | 97 | return value; 98 | } 99 | 100 | public static SHA1 submit(SHA1 value) { 101 | 102 | if (!algorithm.isCurrent(value)) { 103 | throw new GdxRuntimeException(""); 104 | } 105 | 106 | try { 107 | algorithm.digest.get().digest(value.value, 0, 20); 108 | } catch (DigestException e) { 109 | throw new GdxRuntimeException(e); 110 | } 111 | 112 | algorithm.makeCurrent(null); 113 | 114 | return value; 115 | } 116 | 117 | @Override 118 | public String toString() { 119 | 120 | StringBuilder builder = algorithm.stringBuilder.get(); 121 | builder.setLength(0); 122 | 123 | int v; 124 | char c; 125 | 126 | for (byte b : value) { 127 | v = (b & 0xf0) >> 4; 128 | c = (v < 10) ? (char) ('0' + v) : (char) ('a' + v - 10); 129 | builder.append(c); 130 | v = b & 0x0f; 131 | c = (v < 10) ? (char) ('0' + v) : (char) ('a' + v - 10); 132 | builder.append(c); 133 | } 134 | 135 | return builder.toString(); 136 | } 137 | 138 | /** 139 | * (Re-)use thread-local instances of {@link MessageDigest} 140 | */ 141 | private static class Algorithm { 142 | 143 | ThreadLocal digest = new ThreadLocal() { 144 | @Override 145 | protected MessageDigest initialValue() { 146 | try { 147 | return MessageDigest.getInstance("SHA-1"); 148 | } catch (NoSuchAlgorithmException e) { 149 | throw new GdxRuntimeException(e); 150 | } 151 | } 152 | }; 153 | 154 | ThreadLocal currentHash = new ThreadLocal() { 155 | @Override 156 | protected SHA1 initialValue() { 157 | return null; 158 | } 159 | }; 160 | 161 | ThreadLocal stringBuilder = new ThreadLocal() { 162 | @Override 163 | protected StringBuilder initialValue() { 164 | return new StringBuilder(40); 165 | } 166 | }; 167 | 168 | void makeCurrent(SHA1 currentHash) { 169 | this.currentHash.set(currentHash); 170 | } 171 | 172 | boolean isCurrent(SHA1 currentHash) { 173 | return this.currentHash.get() == currentHash; 174 | } 175 | } 176 | 177 | private static final Algorithm algorithm = new Algorithm(); 178 | 179 | } 180 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/checksum/SHA1FileTable.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.checksum; 2 | 3 | import com.badlogic.gdx.files.*; 4 | import com.badlogic.gdx.utils.Array; 5 | import com.badlogic.gdx.utils.ObjectMap; 6 | 7 | import java.io.*; 8 | 9 | public class SHA1FileTable { 10 | 11 | public enum CheckFileResult { 12 | Unchecked, // file has not been checked yet 13 | Unmodified, // file not changed 14 | Modified, // file content has been modified 15 | NoSHA1SumFound, // file not yet known (may be new) 16 | FileNotFound // file not found (may be deleted) 17 | } 18 | 19 | private static class Entry { 20 | 21 | String filePath; 22 | SHA1 sha1; 23 | CheckFileResult checkResult; 24 | 25 | Entry(String filePath, SHA1 sha1) { 26 | this.filePath = filePath; 27 | this.sha1 = sha1; 28 | this.checkResult = CheckFileResult.Unchecked; 29 | } 30 | } 31 | 32 | private final ObjectMap entries = new ObjectMap<>(); 33 | 34 | public SHA1FileTable() throws IOException { 35 | this(null); 36 | } 37 | 38 | public int size() { 39 | return entries.size; 40 | } 41 | 42 | /** 43 | * Initializes the file/hash table from a .sha1sum text file. 44 | */ 45 | public SHA1FileTable(FileHandle sha1sumFile) throws IOException { 46 | 47 | if (sha1sumFile == null || !sha1sumFile.exists()) { 48 | return; 49 | } 50 | 51 | TextFileUtils.readLines( 52 | sha1sumFile, 53 | new String[] { "^[0-9a-fA-F]+\\s+[\\S]+$" }, 54 | line -> { 55 | 56 | String[] split = line.split(" "); 57 | 58 | String path = split[split.length - 1]; 59 | String digest = split[0]; 60 | 61 | entries.put(path, new Entry(path, SHA1.valueOf(digest))); 62 | 63 | }); 64 | } 65 | 66 | /** 67 | * Saves the file/hash table as .sha1sum text file. The output format is compatible to the *NIX 'sha1sum' 68 | * command line tool. 69 | */ 70 | public void save(FileHandle sha1sumFile) throws IOException { 71 | 72 | BufferedWriter writer = new BufferedWriter(new FileWriter(sha1sumFile.file())); 73 | 74 | Array sortedValues = entries.values().toArray(); 75 | sortedValues.sort((value1, value2) -> value1.filePath.compareTo(value2.filePath)); 76 | 77 | for (Entry entry : sortedValues) { 78 | 79 | writer.write(entry.sha1.toString()); 80 | writer.write(" "); 81 | writer.write(entry.filePath); 82 | writer.write("\n"); 83 | 84 | } 85 | 86 | writer.flush(); 87 | writer.close(); 88 | } 89 | 90 | /** 91 | * Checks the SHA1 hash of a file against the SHA1 stored in the table. 92 | */ 93 | public CheckFileResult checkFile(File file) { 94 | 95 | if (!isKnownFile(file)) { 96 | return CheckFileResult.NoSHA1SumFound; 97 | } 98 | 99 | try { 100 | 101 | Entry entry = entries.get(file.getPath()); 102 | 103 | if (entry.checkResult != CheckFileResult.Unchecked) { 104 | // no need to hash more than once 105 | return entry.checkResult; 106 | } 107 | 108 | SHA1 sha1 = FileStreamReader.hashStream(new FileInputStream(file)); 109 | 110 | if (sha1.equals(entry.sha1)) { 111 | entry.checkResult = CheckFileResult.Unmodified; 112 | } else { 113 | entry.checkResult = CheckFileResult.Modified; 114 | } 115 | 116 | return entry.checkResult; 117 | 118 | } catch (IOException e) { 119 | return CheckFileResult.FileNotFound; 120 | } 121 | 122 | } 123 | 124 | /** 125 | * Adds a file and its hash to the SHA1 table. 126 | */ 127 | public void registerFile(File file) throws IOException { 128 | 129 | SHA1 sha1 = FileStreamReader.hashStream(new FileInputStream(file)); 130 | 131 | if (isKnownFile(file)) { 132 | Entry entry = entries.get(file.getPath()); 133 | entry.sha1 = sha1; 134 | } else { 135 | Entry entry = new Entry(file.getPath(), sha1); 136 | entries.put(file.getPath(), entry); 137 | } 138 | } 139 | 140 | /** 141 | * Removes a table entry. 142 | */ 143 | @Deprecated 144 | public void unregisterFile(File file) { 145 | if (isKnownFile(file)) { 146 | entries.remove(file.getPath()); 147 | } 148 | } 149 | 150 | public boolean hasUncheckedFiles() { 151 | for (Entry entry : entries.values()) { 152 | if (entry.checkResult == CheckFileResult.Unchecked) { 153 | return true; 154 | } 155 | } 156 | return false; 157 | } 158 | 159 | private boolean isKnownFile(File file) { 160 | return entries.containsKey(file.getPath()); 161 | } 162 | 163 | } 164 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/concurrent/AsyncTask.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.concurrent; 2 | 3 | import com.badlogic.gdx.function.Consumer; 4 | import com.badlogic.gdx.function.Predicate; 5 | 6 | import java.util.concurrent.*; 7 | import java.util.concurrent.atomic.AtomicReference; 8 | 9 | /** 10 | * A reentrant wrapper to {@link FutureTask}, with some additional properties to synchronize its result with the 11 | * calling thread. 12 | *

13 | * The task is (re-)scheduled for asynchronous execution with {@link AsyncTask#execute(ExecutorService)}. 14 | *

15 | * Upon completion, a {@link CyclicBarrier} is entered, waiting for the scheduling thread to call 16 | * {@link AsyncTask#await(Consumer)}. After both threads have entered the barrier, {@link AsyncTaskJob#completed()} 17 | * is called. 18 | */ 19 | public class AsyncTask> { 20 | 21 | private enum State { 22 | READY, 23 | PENDING, 24 | COMPLETED 25 | } 26 | 27 | protected final V job; 28 | 29 | private final CyclicBarrier completionBarrier; 30 | 31 | private final AtomicReference state = new AtomicReference<>(State.READY); 32 | 33 | private Task task; 34 | 35 | public AsyncTask(V job) { 36 | this.job = job; 37 | completionBarrier = new CyclicBarrier(2, this::completed); 38 | } 39 | 40 | public boolean consumeJobPredicate(Predicate consumer) { 41 | 42 | if (state.get() != State.READY) { 43 | //throw new IllegalStateException("Invalid task state!"); 44 | return false; 45 | } 46 | 47 | return consumer.test(job); 48 | } 49 | 50 | public void consumeJob(Consumer consumer) { 51 | 52 | if (state.get() != State.READY) { 53 | //throw new IllegalStateException("Invalid task state!"); 54 | return; 55 | } 56 | 57 | consumer.accept(job); 58 | } 59 | 60 | public boolean isReady() { 61 | return state.get() == State.READY; 62 | } 63 | 64 | public boolean isPending() { 65 | return state.get() == State.PENDING; 66 | } 67 | 68 | public boolean isCompleted() { 69 | return state.get() == State.COMPLETED; 70 | } 71 | 72 | /** 73 | * Enters the task's completion barrier, waiting for {@link AsyncTaskJob#completed()} to be called. 74 | * 75 | * This function blocks execution if the task is still pending. Use {@link AsyncTask#isCompleted()} 76 | * for a non-blocking check. 77 | * 78 | * Returns the arrival index of the current thread, see {@link CyclicBarrier#await()}. 79 | */ 80 | public int await(Consumer consumeAfterCompletion) throws InterruptedException { 81 | 82 | if (state.get() == State.READY) { 83 | throw new IllegalStateException("Invalid task state!"); 84 | } 85 | 86 | try { 87 | 88 | int arrivalIndex = completionBarrier.await(); 89 | 90 | task.get(); // this causes an ExecutionException if there has been some error 91 | 92 | if (consumeAfterCompletion != null) { 93 | consumeAfterCompletion.accept(job); 94 | } 95 | 96 | if (!state.compareAndSet(State.COMPLETED, State.READY)) { 97 | throw new IllegalStateException("Invalid task state!"); 98 | } 99 | 100 | return arrivalIndex; 101 | 102 | } catch (BrokenBarrierException e) { 103 | throw new InterruptedException(e.getMessage()); 104 | } catch (ExecutionException e) { 105 | throw new RuntimeException("Exception thrown during execution of asynchronous task!", e); 106 | } 107 | } 108 | 109 | /** 110 | * Queue the task for execution by the given {@link ExecutorService}. 111 | * 112 | * @throws IllegalStateException if the task is not ready yet, after it has been scheduled previously. 113 | */ 114 | void execute(ExecutorService service) { 115 | 116 | // reset state 117 | if (!state.compareAndSet(State.READY, State.PENDING)) { 118 | throw new IllegalStateException("Invalid task state!"); 119 | } 120 | 121 | completionBarrier.reset(); 122 | 123 | // pass to executor service 124 | service.execute(task = new Task()); 125 | } 126 | 127 | /** 128 | * Called from {@link CyclicBarrier} when the async task is completed. 129 | */ 130 | private void completed() { 131 | job.completed(); 132 | } 133 | 134 | private class Task extends FutureTask { 135 | 136 | Task() { 137 | super(job); 138 | } 139 | 140 | @Override 141 | protected void done() { 142 | 143 | try { 144 | 145 | if (!state.compareAndSet(State.PENDING, State.COMPLETED)) { 146 | throw new IllegalStateException("Invalid completion state!"); 147 | } 148 | 149 | completionBarrier.await(); 150 | 151 | } catch (InterruptedException | BrokenBarrierException e) { 152 | throw new IllegalStateException(e); 153 | } 154 | 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/concurrent/AsyncTaskExecutor.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.concurrent; 2 | 3 | import com.badlogic.gdx.utils.Disposable; 4 | import com.badlogic.gdx.utils.GdxSnippets; 5 | 6 | import java.util.concurrent.*; 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | /** 10 | * A wrapper to {@link ExecutorService} which executes {@link AsyncTask} tasks. 11 | */ 12 | public class AsyncTaskExecutor implements Disposable { 13 | 14 | private final ExecutorService service; 15 | 16 | public AsyncTaskExecutor(int threadCount, String threadNamePrefix) { 17 | 18 | threadCount = Math.max(threadCount, 1); 19 | GdxSnippets.log.info("Starting {} with {} threads.", threadNamePrefix, threadCount); 20 | 21 | service = new FixedThreadPoolExecutor(threadCount, new Factory(threadNamePrefix)); 22 | } 23 | 24 | public > 25 | void execute(AsyncTask task) { 26 | task.execute(service); 27 | } 28 | 29 | /** 30 | * fire & forget - no synchronization with main thread is done 31 | */ 32 | public void executeJob(Runnable job) { 33 | service.execute(job); 34 | } 35 | 36 | @Override 37 | public void dispose() { 38 | 39 | service.shutdown(); 40 | GdxSnippets.log.info("Shutting down AsyncTaskExecutor"); 41 | 42 | try { 43 | service.awaitTermination(2500, TimeUnit.MILLISECONDS); 44 | } catch (InterruptedException e) { 45 | e.printStackTrace(); 46 | } 47 | } 48 | 49 | private static class Factory implements ThreadFactory { 50 | 51 | private final ThreadGroup group; 52 | private final AtomicInteger threadNumber = new AtomicInteger(1); 53 | private final String namePrefix; 54 | 55 | Factory(String prefix) { 56 | group = Thread.currentThread().getThreadGroup(); 57 | namePrefix = prefix; 58 | } 59 | 60 | @Override 61 | public Thread newThread(Runnable runnable) { 62 | Thread thread = new Thread(group, runnable, namePrefix + "-" + threadNumber.getAndIncrement()); 63 | thread.setDaemon(true); 64 | return thread; 65 | } 66 | } 67 | 68 | private static class FixedThreadPoolExecutor extends ThreadPoolExecutor { 69 | 70 | FixedThreadPoolExecutor(int nThreads, ThreadFactory threadFactory) { 71 | super(nThreads, nThreads, 72 | 0L, TimeUnit.MILLISECONDS, 73 | new LinkedBlockingQueue<>(), 74 | threadFactory); 75 | } 76 | 77 | @Override 78 | protected void afterExecute(Runnable r, Throwable t) { 79 | super.afterExecute(r, t); 80 | if (t == null && r instanceof Future) { 81 | try { 82 | Object result = ((Future) r).get(); 83 | } catch (CancellationException ce) { 84 | t = ce; 85 | } catch (ExecutionException ee) { 86 | t = ee.getCause(); 87 | } catch (InterruptedException ie) { 88 | Thread.currentThread().interrupt(); // ignore/reset 89 | } 90 | } 91 | if (t != null) { 92 | GdxSnippets.log.error("thread pool execution error", t); 93 | } 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/concurrent/AsyncTaskJob.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.concurrent; 2 | 3 | import java.util.concurrent.Callable; 4 | 5 | public interface AsyncTaskJob extends Callable { 6 | 7 | void completed(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/concurrent/ReentrantLock.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.concurrent; 2 | 3 | import com.badlogic.gdx.function.Consumer; 4 | import com.badlogic.gdx.function.Supplier; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.concurrent.locks.Lock; 8 | 9 | /** 10 | * A functional style wrapper to {@link java.util.concurrent.locks.ReentrantLock}. 11 | */ 12 | public class ReentrantLock { 13 | 14 | private final Lock lock = new java.util.concurrent.locks.ReentrantLock(); 15 | 16 | public void lock(Runnable runnable) { 17 | try { 18 | lock.lock(); 19 | runnable.run(); 20 | } finally { 21 | lock.unlock(); 22 | } 23 | } 24 | 25 | public void lock(C context, Consumer consumer) { 26 | try { 27 | lock.lock(); 28 | consumer.accept(context); 29 | } finally { 30 | lock.unlock(); 31 | } 32 | } 33 | 34 | public T lock(Supplier supplier) { 35 | try { 36 | lock.lock(); 37 | return supplier.get(); 38 | } finally { 39 | lock.unlock(); 40 | } 41 | } 42 | 43 | public T tryLock(Supplier supplier, T defaultValue) { 44 | if (!lock.tryLock()) { 45 | return defaultValue; 46 | } 47 | try { 48 | return supplier.get(); 49 | } finally { 50 | lock.unlock(); 51 | } 52 | } 53 | 54 | public T tryLock(Supplier supplier, T defaultValue, long time, TimeUnit unit) throws InterruptedException { 55 | if (!lock.tryLock(time, unit)) { 56 | return defaultValue; 57 | } 58 | try { 59 | return supplier.get(); 60 | } finally { 61 | lock.unlock(); 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/concurrent/ReentrantReadWriteLock.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.concurrent; 2 | 3 | import com.badlogic.gdx.function.Consumer; 4 | import com.badlogic.gdx.function.Supplier; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.concurrent.locks.ReadWriteLock; 8 | 9 | /** 10 | * A functional style wrapper to {@link java.util.concurrent.locks.ReentrantReadWriteLock}. 11 | */ 12 | public class ReentrantReadWriteLock { 13 | 14 | private final ReadWriteLock lock = new java.util.concurrent.locks.ReentrantReadWriteLock(); 15 | 16 | public void read(Runnable runnable) { 17 | try { 18 | lock.readLock().lock(); 19 | runnable.run(); 20 | } finally { 21 | lock.readLock().unlock(); 22 | } 23 | } 24 | 25 | public void read(C context, Consumer consumer) { 26 | try { 27 | lock.readLock().lock(); 28 | consumer.accept(context); 29 | } finally { 30 | lock.readLock().unlock(); 31 | } 32 | } 33 | 34 | public T read(Supplier supplier) { 35 | try { 36 | lock.readLock().lock(); 37 | return supplier.get(); 38 | } finally { 39 | lock.readLock().unlock(); 40 | } 41 | } 42 | 43 | public T tryRead(Supplier supplier, T defaultValue) { 44 | if (!lock.readLock().tryLock()) { 45 | return defaultValue; 46 | } 47 | try { 48 | return supplier.get(); 49 | } finally { 50 | lock.readLock().unlock(); 51 | } 52 | } 53 | 54 | public T tryRead(Supplier supplier, T defaultValue, long time, TimeUnit unit) throws InterruptedException { 55 | if (!lock.readLock().tryLock(time, unit)) { 56 | return defaultValue; 57 | } 58 | try { 59 | return supplier.get(); 60 | } finally { 61 | lock.readLock().unlock(); 62 | } 63 | } 64 | 65 | public void write(Runnable runnable) { 66 | try { 67 | lock.writeLock().lock(); 68 | runnable.run(); 69 | } finally { 70 | lock.writeLock().unlock(); 71 | } 72 | } 73 | 74 | public T write(Supplier supplier) { 75 | try { 76 | lock.writeLock().lock(); 77 | return supplier.get(); 78 | } finally { 79 | lock.writeLock().unlock(); 80 | } 81 | } 82 | 83 | public T tryWrite(Supplier supplier, T defaultValue) { 84 | if (!lock.writeLock().tryLock()) { 85 | return defaultValue; 86 | } 87 | try { 88 | return supplier.get(); 89 | } finally { 90 | lock.writeLock().unlock(); 91 | } 92 | } 93 | 94 | public T tryWrite(Supplier supplier, T defaultValue, long time, TimeUnit unit) throws InterruptedException { 95 | if (!lock.writeLock().tryLock(time, unit)) { 96 | return defaultValue; 97 | } 98 | try { 99 | return supplier.get(); 100 | } finally { 101 | lock.writeLock().unlock(); 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/concurrent/ThreadLocalArray.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.concurrent; 2 | 3 | import com.badlogic.gdx.function.Supplier; 4 | import com.badlogic.gdx.function.ThrowableSupplier; 5 | import com.badlogic.gdx.utils.GdxRuntimeException; 6 | import com.badlogic.gdx.utils.reflect.*; 7 | 8 | /** 9 | * A convenience wrapper to {@link ThreadLocal} storing an array of objects. 10 | * 11 | *

12 |  * {@code
13 |  * ThreadLocalArray tls = new ThreadLocalArray<>(64, UserObject.class, () -> new UserObject(params...));
14 |  * UserObject[] array = tls.get();
15 |  * }
16 |  * 
17 | */ 18 | public class ThreadLocalArray implements Supplier { 19 | 20 | private final ThreadLocal tls = new ThreadLocal<>(); 21 | 22 | public ThreadLocalArray(int capacity, Class clazz) { 23 | this(capacity, clazz, () -> ClassReflection.newInstance(clazz)); 24 | } 25 | 26 | @SuppressWarnings("unchecked") 27 | public ThreadLocalArray(int capacity, Class clazz, 28 | ThrowableSupplier initialValueSupplier) { 29 | 30 | try { 31 | 32 | T[] values = (T[]) ArrayReflection.newInstance(clazz, capacity); 33 | 34 | for (int i = 0; i < capacity; i++) { 35 | values[i] = initialValueSupplier.get(); 36 | } 37 | 38 | tls.set(values); 39 | 40 | } catch (ReflectionException e) { 41 | throw new GdxRuntimeException(e); 42 | } 43 | } 44 | 45 | @Override 46 | public T[] get() { 47 | return tls.get(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/concurrent/ThreadLocalInstance.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.concurrent; 2 | 3 | import com.badlogic.gdx.function.Supplier; 4 | 5 | /** 6 | * Substitute for {@link ThreadLocal#withInitial(Supplier)} 7 | */ 8 | public class ThreadLocalInstance extends ThreadLocal { 9 | 10 | private final Supplier supplier; 11 | 12 | public ThreadLocalInstance(Supplier supplier) { 13 | this.supplier = supplier; 14 | } 15 | 16 | @Override 17 | protected T initialValue() { 18 | return supplier.get(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/files/FileStreamConsumer.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.files; 2 | 3 | import java.io.IOException; 4 | 5 | @FunctionalInterface 6 | public interface FileStreamConsumer { 7 | 8 | void read(byte[] bytes, int length) throws IOException; 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/files/FileStreamReader.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.files; 2 | 3 | import com.badlogic.gdx.checksum.SHA1; 4 | 5 | import java.io.*; 6 | 7 | public final class FileStreamReader { 8 | 9 | /** 10 | * Uses a {@link BufferedInputStream} to consume an {@link InputStream}. 11 | */ 12 | public static void readStream(InputStream stream, 13 | int cacheSize, 14 | FileStreamConsumer consumer) throws IOException { 15 | 16 | try (BufferedInputStream bis = new BufferedInputStream(stream)) { 17 | 18 | int n = 0; 19 | byte[] buffer = new byte[cacheSize]; 20 | 21 | while (n != -1) { 22 | 23 | n = bis.read(buffer); 24 | 25 | if (n > 0) { 26 | consumer.read(buffer, n); 27 | } 28 | } 29 | } 30 | } 31 | 32 | /** 33 | * Calculates the SHA-1 hash on a {@link InputStream}. 34 | */ 35 | public static SHA1 hashStream(InputStream stream) throws IOException { 36 | 37 | SHA1 sha1 = SHA1.create(); 38 | 39 | readStream(stream, 1024, 40 | (bytes, n) -> SHA1.update(sha1, bytes, 0, n)); 41 | 42 | SHA1.submit(sha1); 43 | 44 | return sha1; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/files/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.files; 2 | 3 | import com.badlogic.gdx.Files.FileType; 4 | import com.badlogic.gdx.utils.Host; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.nio.file.Path; 9 | import java.nio.file.Paths; 10 | import java.util.Arrays; 11 | import java.util.Comparator; 12 | import java.util.regex.Matcher; 13 | import java.util.regex.Pattern; 14 | 15 | import static com.badlogic.gdx.utils.Host.OS.Linux; 16 | 17 | public final class FileUtils { 18 | 19 | /** 20 | * Default comparator to sort {@link FileHandle}s: 21 | * - directories first 22 | * - then by name, case insensitive 23 | */ 24 | public static final Comparator DEFAULT_COMPARATOR = (file1, file2) -> { 25 | 26 | boolean d1 = file1.isDirectory(); 27 | boolean d2 = file2.isDirectory(); 28 | 29 | if (d1 != d2) { 30 | return d1 ? -1 : 1; 31 | } 32 | 33 | return file1.name().compareToIgnoreCase(file2.name()); 34 | }; 35 | 36 | /** 37 | * Wrapper to {@link FileHandle#list()} which also sorts the result. 38 | */ 39 | public static FileHandle[] list(FileHandle folder) { 40 | 41 | FileHandle[] files = folder.list(); 42 | Arrays.sort(files, DEFAULT_COMPARATOR); 43 | 44 | return files; 45 | } 46 | 47 | /** 48 | * Wrapper to {@link FileHandle#list(String)} which also sorts the result. 49 | */ 50 | public static FileHandle[] list(FileHandle folder, String suffix) { 51 | 52 | FileHandle[] files = folder.list(suffix); 53 | Arrays.sort(files, DEFAULT_COMPARATOR); 54 | 55 | return files; 56 | } 57 | 58 | /** 59 | * Queries (and creates, if necessary) a path to store user files. 60 | *

61 | * Linux: 62 | * - $XDG_DATA_HOME 63 | * - user.home/.local/share 64 | * - $HOME/.local/share 65 | * MacOS: 66 | * - $XDG_DATA_HOME 67 | * - user.home/Library/Application Support 68 | * - $HOME/Library/Application Support 69 | * Windows: 70 | * - %LOCALAPPDATA% 71 | * - %APPDATA% 72 | */ 73 | public static FileHandle getUserFolder(String companyIdentifier, 74 | String... productFolders) throws IOException { 75 | 76 | Path path = null; 77 | 78 | switch (Host.os) { 79 | 80 | case Linux: { 81 | 82 | // $XDG_DATA_HOME 83 | 84 | String dataDir = System.getenv("XDG_DATA_HOME"); 85 | 86 | if (dataDir != null && !dataDir.isEmpty()) { 87 | 88 | path = Paths.get(dataDir); 89 | 90 | if (path.toFile().isDirectory()) { 91 | break; 92 | } 93 | 94 | path = null; 95 | } 96 | 97 | // [user.home | $HOME]/.local/share 98 | 99 | String homeDir = System.getProperty("user.home"); 100 | 101 | if (homeDir == null || homeDir.isEmpty()) { 102 | homeDir = System.getenv("HOME"); 103 | } 104 | 105 | if (homeDir != null && !homeDir.isEmpty()) { 106 | 107 | path = Paths.get(homeDir, ".local", "share"); 108 | 109 | if (path.toFile().isDirectory()) { 110 | break; 111 | } 112 | 113 | path = null; 114 | } 115 | 116 | break; 117 | } 118 | 119 | case MacOS: { 120 | 121 | // $XDG_DATA_HOME 122 | 123 | String dataDir = System.getenv("XDG_DATA_HOME"); 124 | 125 | if (dataDir != null && !dataDir.isEmpty()) { 126 | 127 | path = Paths.get(dataDir); 128 | 129 | if (path.toFile().isDirectory()) { 130 | break; 131 | } 132 | 133 | path = null; 134 | } 135 | 136 | // [user.home | $HOME]/Library/"Application Support" 137 | 138 | String homeDir = System.getProperty("user.home"); 139 | 140 | if (homeDir == null || homeDir.isEmpty()) { 141 | homeDir = System.getenv("HOME"); 142 | } 143 | 144 | if (homeDir != null && !homeDir.isEmpty()) { 145 | 146 | path = Paths.get(homeDir, "Library", "Application Support"); 147 | 148 | if (path.toFile().isDirectory()) { 149 | break; 150 | } 151 | 152 | path = null; 153 | } 154 | 155 | break; 156 | } 157 | 158 | case Windows: 159 | 160 | // [%LOCALAPPDATA% | %APPDATA%] 161 | 162 | String appData = System.getenv("LOCALAPPDATA"); 163 | 164 | if (appData == null || appData.isEmpty()) { 165 | appData = System.getenv("APPDATA"); 166 | } 167 | 168 | if (appData != null && !appData.isEmpty()) { 169 | 170 | path = Paths.get(appData); 171 | 172 | if (path.toFile().isDirectory()) { 173 | break; 174 | } 175 | 176 | path = null; 177 | } 178 | 179 | break; 180 | } 181 | 182 | if (path == null) { 183 | throw new IOException("no valid user data folder found"); 184 | } 185 | 186 | // company sub-folder 187 | 188 | path = path.resolve(Host.os == Linux ? companyIdentifier.toLowerCase() : companyIdentifier); 189 | 190 | // append remaining user-defined folders 191 | 192 | for (String folder : productFolders) { 193 | path = path.resolve(folder); 194 | } 195 | 196 | // create if necessary 197 | 198 | FileHandle fileHandle = newFileHandle(path.toFile(), FileType.Absolute); 199 | 200 | if (!fileHandle.exists()) { 201 | fileHandle.mkdirs(); 202 | } 203 | 204 | return fileHandle; 205 | } 206 | 207 | public static FileHandle newFileHandle(File file, FileType type) { 208 | return new FileHandleHelper(file, type); 209 | } 210 | 211 | /** 212 | * Normalizes the path of a file handle. 213 | *

214 | * This does some hoops to work around some restrictions of the {@link FileHandle} class. 215 | */ 216 | public static FileHandle normalize(FileHandle file) { 217 | return new FileHandleHelper(file).normalize(); 218 | } 219 | 220 | private static class FileHandleHelper extends FileHandle { 221 | 222 | private static final Pattern relative = Pattern.compile("(/[a-zA-Z0-9\\-_]+/\\.\\.)"); 223 | 224 | FileHandleHelper(FileHandle file) { 225 | super(file.file(), file.type()); 226 | } 227 | 228 | FileHandleHelper(File file, FileType type) { 229 | super(file, type); 230 | } 231 | 232 | FileHandleHelper normalize() { 233 | String path = file.getPath(); 234 | if (path.startsWith("..")) { 235 | throw new IllegalStateException(); 236 | } 237 | // normalize path separators first 238 | path = path.replaceAll("\\\\", "/"); 239 | // extract any "//.." 240 | for (;;) { 241 | Matcher matcher = relative.matcher(path); 242 | if (!matcher.find()) { 243 | break; 244 | } 245 | String match = matcher.group(0); 246 | path = path.replace(match, ""); 247 | } 248 | file = new File(path); 249 | return this; 250 | } 251 | } 252 | 253 | } 254 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/files/TextFileUtils.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.files; 2 | 3 | import com.badlogic.gdx.function.ThrowableConsumer; 4 | import com.badlogic.gdx.utils.*; 5 | import com.badlogic.gdx.utils.StringBuilder; 6 | 7 | import java.io.*; 8 | import java.util.regex.Pattern; 9 | 10 | public class TextFileUtils { 11 | 12 | /** 13 | * Reads the file as text, passing its content to the consumer function, line by line. 14 | */ 15 | public static void readLines(FileHandle file, 16 | ThrowableConsumer consumer) throws IOException { 17 | 18 | readLines(file, null, consumer); 19 | } 20 | 21 | /** 22 | * Reads the file as text, filtering each line by a user-defined set of RegEx patterns. 23 | * The line is passed to the consumer function only if (at least) one of the patterns matches. 24 | */ 25 | public static void readLines(FileHandle file, String[] patterns, 26 | ThrowableConsumer consumer) throws IOException { 27 | 28 | Pattern[] p = null; 29 | int lineNo = 0; 30 | 31 | if (!ArrayUtils.isNullOrEmpty(patterns)) { 32 | p = new Pattern[patterns.length]; 33 | for (int i = 0; i < patterns.length; i++) { 34 | p[i] = Pattern.compile(patterns[i]); 35 | } 36 | } 37 | 38 | try (BufferedReader reader = new BufferedReader(file.reader())) { 39 | 40 | String line; 41 | while ((line = reader.readLine()) != null) { 42 | 43 | lineNo++; 44 | 45 | if (p == null) { 46 | consumer.accept(line); 47 | } else { 48 | for (int i = 0; i < patterns.length; i++) { 49 | if (p[i].matcher(line).matches()) { 50 | consumer.accept(line); 51 | break; 52 | } 53 | } 54 | } 55 | } 56 | 57 | } catch (IOException e) { 58 | throw new IOException("Error reading " + file.path() + " at line #" + lineNo, e); 59 | } 60 | } 61 | 62 | public static String readString(FileHandle file) throws IOException { 63 | 64 | StringBuilder builder = new StringBuilder((int) file.length()); 65 | 66 | readLines(file, line -> { 67 | 68 | if (builder.length() > 0) { 69 | builder.append('\n'); 70 | } 71 | 72 | builder.append(line); 73 | }); 74 | 75 | return builder.toString(); 76 | } 77 | 78 | public static void writeString(FileHandle file, String text) throws IOException { 79 | 80 | StringBuilder builder = new StringBuilder(text.length()); 81 | String newLine = Host.os != Host.OS.Windows ? "\n" : "\r\n"; 82 | 83 | try (BufferedReader reader = new BufferedReader(new StringReader(text))) { 84 | 85 | String line; 86 | while ((line = reader.readLine()) != null) { 87 | 88 | if (builder.length() > 0) { 89 | builder.append(newLine); 90 | } 91 | 92 | builder.append(line); 93 | } 94 | } 95 | 96 | file.writeString(builder.toString(), false); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/BiConsumer.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | @FunctionalInterface 4 | public interface BiConsumer { 5 | void accept(T t, U u); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/BiFunction.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | @FunctionalInterface 4 | public interface BiFunction { 5 | R apply(T t, U u); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/BiPredicate.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | @FunctionalInterface 4 | public interface BiPredicate { 5 | boolean test(T t, U u); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/BooleanSupplier.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | @FunctionalInterface 4 | public interface BooleanSupplier { 5 | boolean getAsBoolean(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/Consumer.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | @FunctionalInterface 4 | public interface Consumer { 5 | void accept(T t); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/Function.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | @FunctionalInterface 4 | public interface Function { 5 | R apply(T t); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/Functions.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | import java.util.Comparator; 4 | 5 | public final class Functions { 6 | 7 | public static final Runnable NOOP_RUNNABLE = () -> { 8 | }; 9 | 10 | @SuppressWarnings("ComparatorCombinators") 11 | public static > Comparator comparing( 12 | Function fn) { 13 | return (o1, o2) -> fn.apply(o1).compareTo(fn.apply(o2)); 14 | } 15 | 16 | @SuppressWarnings("ComparatorCombinators") 17 | public static Comparator comparingInt(ToIntFunction fn) { 18 | return (o1, o2) -> Integer.compare(fn.applyAsInt(o1), fn.applyAsInt(o2)); 19 | } 20 | 21 | public static Comparator comparingIntDescending(ToIntFunction fn) { 22 | return (o1, o2) -> Integer.compare(fn.applyAsInt(o2), fn.applyAsInt(o1)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/IntConsumer.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | @FunctionalInterface 4 | public interface IntConsumer { 5 | void accept(int value); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/IntFunction.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | @FunctionalInterface 4 | public interface IntFunction { 5 | R apply(int value); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/IntObjConsumer.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | /** 4 | * Similar to {@link java.util.function.ObjIntConsumer}. 5 | */ 6 | @FunctionalInterface 7 | public interface IntObjConsumer { 8 | 9 | void accept(int value, T t); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/IntPredicate.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | @FunctionalInterface 4 | public interface IntPredicate { 5 | boolean test(int value); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/Iterables.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | public final class Iterables { 4 | 5 | /** 6 | * Substitute for {@link Iterable#forEach(java.util.function.Consumer)}. 7 | */ 8 | public static void forEach(Iterable iterable, Consumer action) { 9 | for (T t : iterable) { 10 | action.accept(t); 11 | } 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/Predicate.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | @FunctionalInterface 4 | public interface Predicate { 5 | boolean test(T t); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/Supplier.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | @FunctionalInterface 4 | public interface Supplier { 5 | T get(); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/ThrowableBiFunction.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | @FunctionalInterface 4 | public interface ThrowableBiFunction { 5 | 6 | R apply(T t, U u) throws E; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/ThrowableConsumer.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | /** 4 | * Version of {@link Consumer} which can throw an exception. 5 | * 6 | * @param the type of parameter passed to this consumer 7 | * @param the type of exception to be handled 8 | */ 9 | @FunctionalInterface 10 | public interface ThrowableConsumer { 11 | void accept(T t) throws E; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/ThrowableFunction.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | /** 4 | * Version of {@link Function} which can throw an exception. 5 | * 6 | * @param the type of parameter passed to this consumer 7 | * @param the type of the function result 8 | * @param the type of exception to be handled 9 | */ 10 | @FunctionalInterface 11 | public interface ThrowableFunction { 12 | 13 | R apply(T t) throws E; 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/ThrowableSupplier.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | /** 4 | * A variant of {@link Supplier} which can throw an exception. 5 | * 6 | * @param the type of result supplied by this supplier 7 | * @param the type of exception to be handled 8 | */ 9 | @FunctionalInterface 10 | public interface ThrowableSupplier { 11 | T get() throws E; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/ToBooleanFunction.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | /** 4 | * Represents a function that produces an boolean-valued result. This is a 5 | * {@code boolean}-producing primitive specialization for {@link Function}. 6 | * 7 | * @param the type of the input to the function 8 | * @see Function 9 | */ 10 | @FunctionalInterface 11 | public interface ToBooleanFunction { 12 | 13 | /** 14 | * Applies this function to the given argument. 15 | * 16 | * @param value the function argument 17 | * @return the function result 18 | */ 19 | boolean applyAsBoolean(T value); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/ToByteBiFunction.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | @FunctionalInterface 4 | public interface ToByteBiFunction { 5 | 6 | /** 7 | * Applies this function to the given arguments. 8 | */ 9 | byte applyAsByte(T t, U u); 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/function/ToIntFunction.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.function; 2 | 3 | @FunctionalInterface 4 | public interface ToIntFunction { 5 | int applyAsInt(T t); 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/graphics/GL33Ext.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.graphics; 2 | 3 | import com.badlogic.gdx.Gdx; 4 | import com.badlogic.gdx.utils.GdxSnippets; 5 | 6 | import java.nio.LongBuffer; 7 | 8 | /** 9 | * Extension to Gdx.gl30 which adds functions and constants not available to OpenGL ES, and 10 | * therefore not exposed through the libGDX interface. 11 | */ 12 | public final class GL33Ext { 13 | 14 | public static final int GL_TEXTURE_BORDER_COLOR = 0x1004; 15 | 16 | public static final int GL_POINT = 0x1B00; 17 | public static final int GL_LINE = 0x1B01; 18 | public static final int GL_FILL = 0x1B02; 19 | 20 | public static final int GL_CLAMP_TO_BORDER = 0x812D; 21 | 22 | public static final int GL_CLIP_DISTANCE0 = 0x3000; 23 | public static final int GL_CLIP_DISTANCE1 = 0x3001; 24 | public static final int GL_CLIP_DISTANCE2 = 0x3002; 25 | public static final int GL_CLIP_DISTANCE3 = 0x3003; 26 | public static final int GL_CLIP_DISTANCE4 = 0x3004; 27 | public static final int GL_CLIP_DISTANCE5 = 0x3005; 28 | public static final int GL_CLIP_DISTANCE6 = 0x3006; 29 | public static final int GL_CLIP_DISTANCE7 = 0x3007; 30 | 31 | public static final int GL_INTERNALFORMAT_SUPPORTED = 0x826F; 32 | public static final int GL_INTERNALFORMAT_PREFERRED = 0x8270; 33 | 34 | public static void glBlendEquationi(int buffer, int mode) { 35 | 36 | if (!Gdx.graphics.supportsExtension("GL_ARB_draw_buffers_blend")) { 37 | GdxSnippets.log.warn("Extension ARB_draw_buffers_blend not supported!"); 38 | } 39 | 40 | nglBlendEquationi(buffer, mode); 41 | } 42 | 43 | public static void glGetInternalFormativ(int target, int internalformat, int pname, LongBuffer params) { 44 | 45 | if (!Gdx.graphics.supportsExtension("GL_ARB_internalformat_query2")) { 46 | GdxSnippets.log.warn("Extension ARB_internalformat_query2 not supported!"); 47 | } 48 | 49 | nglGetInternalFormati64v(target, internalformat, pname, params.capacity(), params); 50 | } 51 | 52 | // @off 53 | 54 | /*JNI 55 | #include "flextGL.h" 56 | */ 57 | 58 | public static native void setupGL(boolean setupGLBindings); /* 59 | if (setupGLBindings) { 60 | flextInit(); 61 | } 62 | */ 63 | 64 | public static native void glBindFragDataLocation(int program, int colorNumber, String name); /* 65 | glBindFragDataLocation(program, colorNumber, name); 66 | */ 67 | 68 | private static native void nglBlendEquationi(int buffer, int mode); /* 69 | if (FLEXT_ARB_draw_buffers_blend) { 70 | glpfBlendEquationiARB(buffer, mode); 71 | } 72 | */ 73 | 74 | public static native void glColorMaski(int buffer, boolean r, boolean g, boolean b, boolean a); /* 75 | glColorMaski(buffer, r, g, b, a); 76 | */ 77 | 78 | public static native void glDrawElementsBaseVertex(int mode, int count, int type, int indices, int baseVertex); /* 79 | glDrawElementsBaseVertex(mode, count, type, (const void*) ((size_t) indices), baseVertex); 80 | */ 81 | 82 | private static native void nglGetInternalFormati64v(int target, int internalformat, 83 | int pname, int bufSize, LongBuffer params); /* 84 | if (FLEXT_ARB_internalformat_query2) { 85 | glGetInternalformati64v(target, internalformat, pname, bufSize, (GLint64*) params); 86 | } 87 | */ 88 | 89 | public static native void glPolygonMode(int face, int mode); /* 90 | glPolygonMode(face, mode); 91 | */ 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/graphics/TextureUtils.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.graphics; 2 | 3 | import com.badlogic.gdx.Gdx; 4 | import com.badlogic.gdx.function.Consumer; 5 | import com.badlogic.gdx.utils.Array; 6 | 7 | public class TextureUtils { 8 | 9 | /** 10 | * Calls consumer on each texture currently registered as managed texture. 11 | */ 12 | public static void forEachManagedTexture(Consumer consumer) { 13 | Array textures = Texture.managedTextures.get(Gdx.app); 14 | for (int i = textures.size - 1; i >= 0; i--) { 15 | consumer.accept(textures.get(i)); 16 | } 17 | } 18 | 19 | /** 20 | * Disposes all textures still registered as managed textures. 21 | */ 22 | public static void disposeAllManagedTextures() { 23 | // works because forEachManagedTexture() iterates in reverse order 24 | forEachManagedTexture(Texture::dispose); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/graphics/g2d/PixmapAtlas.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.graphics.g2d; 2 | 3 | import com.badlogic.gdx.files.FileHandle; 4 | import com.badlogic.gdx.function.Iterables; 5 | import com.badlogic.gdx.graphics.Pixmap; 6 | import com.badlogic.gdx.utils.*; 7 | 8 | /** 9 | * Loads images from texture atlas, just like {@link TextureAtlas}, but stores them in pixmaps instead of textures 10 | * to avoid creation of texture resources. 11 | *

12 | * Internally uses {@link com.badlogic.gdx.graphics.g2d.TextureAtlas.TextureAtlasData} to avoid code duplication. 13 | */ 14 | public class PixmapAtlas implements Disposable { 15 | 16 | private final ObjectSet pixmaps = new ObjectSet<>(4); 17 | private final Array pages = new Array<>(); 18 | private final Array regions = new Array<>(); 19 | 20 | public PixmapAtlas(FileHandle packFile) { 21 | this(packFile, packFile.parent(), false); 22 | } 23 | 24 | public PixmapAtlas(FileHandle packFile, PixmapReader reader) { 25 | load(new TextureAtlas.TextureAtlasData(packFile, packFile.parent(), false), reader); 26 | } 27 | 28 | public PixmapAtlas(FileHandle packFile, FileHandle imagesDir, boolean flip) { 29 | load(new TextureAtlas.TextureAtlasData(packFile, imagesDir, flip), DEFAULT_READER); 30 | } 31 | 32 | public PixmapAtlas(TextureAtlas.TextureAtlasData data) { 33 | load(data, DEFAULT_READER); 34 | } 35 | 36 | private PixmapAtlas() { 37 | 38 | } 39 | 40 | private void load(TextureAtlas.TextureAtlasData data, PixmapReader reader) { 41 | 42 | ObjectMap pageToPixmap = new ObjectMap<>(); 43 | 44 | for (int pageIndex = 0; pageIndex < data.pages.size; pageIndex++) { 45 | 46 | TextureAtlas.TextureAtlasData.Page page = data.pages.get(pageIndex); 47 | 48 | Pixmap pixmap = reader.read(page.textureFile, (int) page.width, (int) page.height); 49 | pixmaps.add(pixmap); 50 | 51 | AtlasPage atlasPage = new AtlasPage(pageIndex, page.textureFile, pixmap); 52 | pages.add(atlasPage); 53 | 54 | pageToPixmap.put(page, atlasPage); 55 | } 56 | 57 | for (TextureAtlas.TextureAtlasData.Region region : data.regions) { 58 | 59 | int width = region.width; 60 | int height = region.height; 61 | 62 | AtlasPage atlasPage = pageToPixmap.get(region.page); 63 | 64 | AtlasRegion atlasRegion = new AtlasRegion( 65 | atlasPage.pixmap, 66 | region.left, 67 | region.top, 68 | region.rotate ? height : width, 69 | region.rotate ? width : height); 70 | 71 | atlasRegion.page = atlasPage; 72 | atlasRegion.index = region.index; 73 | atlasRegion.name = region.name; 74 | atlasRegion.rotate = region.rotate; 75 | 76 | regions.add(atlasRegion); 77 | } 78 | } 79 | 80 | @Override 81 | public void dispose() { 82 | Iterables.forEach(pixmaps, Pixmap::dispose); 83 | pixmaps.clear(); 84 | pages.clear(); 85 | regions.clear(); 86 | } 87 | 88 | public ObjectSet getPixmaps() { 89 | return pixmaps; 90 | } 91 | 92 | public Iterable getPages() { 93 | return pages; 94 | } 95 | 96 | public AtlasRegion findRegion(String name) { 97 | for (AtlasRegion region : regions) { 98 | if (region.name.equals(name)) { 99 | return region; 100 | } 101 | } 102 | return null; 103 | } 104 | 105 | public FileHandle getFolder() { 106 | return pages.get(0).fileHandle.parent(); 107 | } 108 | 109 | public static PixmapAtlas createFromPixmap(FileHandle pixmapFile, String regionName) { 110 | 111 | Pixmap pixmap = new Pixmap(pixmapFile); 112 | 113 | PixmapAtlas atlas = new PixmapAtlas(); 114 | 115 | // add single pixmap 116 | atlas.pixmaps.add(pixmap); 117 | 118 | // add one page 119 | AtlasPage atlasPage = new AtlasPage(0, pixmapFile, pixmap); 120 | atlas.pages.add(atlasPage); 121 | 122 | // add one region 123 | int width = pixmap.getWidth(); 124 | int height = pixmap.getHeight(); 125 | 126 | AtlasRegion atlasRegion = new AtlasRegion(pixmap, 0, 0, width, height); 127 | 128 | atlasRegion.page = atlasPage; 129 | atlasRegion.index = 0; 130 | atlasRegion.name = regionName; 131 | atlasRegion.rotate = false; 132 | 133 | atlas.regions.add(atlasRegion); 134 | 135 | return atlas; 136 | } 137 | 138 | public static class AtlasRegion extends PixmapRegion { 139 | 140 | public AtlasPage page; 141 | 142 | // todo: "copy" required fields from TextureAtlas$AtlasRegion 143 | int index; 144 | String name; 145 | boolean rotate; 146 | 147 | AtlasRegion(Pixmap pixmap, int x, int y, int width, int height) { 148 | super(pixmap, x, y, width, height); 149 | } 150 | } 151 | 152 | public static class AtlasPage { 153 | 154 | public final int pageIndex; 155 | public final FileHandle fileHandle; 156 | public final Pixmap pixmap; 157 | 158 | AtlasPage(int pageIndex, FileHandle fileHandle, Pixmap pixmap) { 159 | this.pageIndex = pageIndex; 160 | this.fileHandle = fileHandle; 161 | this.pixmap = pixmap; 162 | } 163 | } 164 | 165 | @FunctionalInterface 166 | public interface PixmapReader { 167 | 168 | Pixmap read(FileHandle file, int width, int height); 169 | } 170 | 171 | private static PixmapReader DEFAULT_READER = (file, width, height) -> new Pixmap(file); 172 | 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/graphics/g2d/PixmapRegion.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.graphics.g2d; 2 | 3 | import com.badlogic.gdx.graphics.Pixmap; 4 | import com.badlogic.gdx.utils.GdxRuntimeException; 5 | 6 | import java.nio.ByteBuffer; 7 | 8 | /** 9 | * Defines a rectangular area of a {@link Pixmap}. 10 | */ 11 | public class PixmapRegion { 12 | 13 | private Pixmap pixmap; 14 | private ByteBuffer pixels; 15 | private int x, y; 16 | private int width, height; 17 | private int pixelStride, lineStride; 18 | 19 | public PixmapRegion() { 20 | 21 | } 22 | 23 | public PixmapRegion(Pixmap pixmap) { 24 | this(pixmap, 0, 0, pixmap.getWidth(), pixmap.getHeight()); 25 | } 26 | 27 | public PixmapRegion(PixmapRegion region) { 28 | setRegion(region); 29 | } 30 | 31 | public PixmapRegion(PixmapRegion region, int x, int y, int width, int height) { 32 | this(region.pixmap, region.x + x, region.y + y, width, height); 33 | } 34 | 35 | public PixmapRegion(Pixmap pixmap, int x, int y, int width, int height) { 36 | setRegion(pixmap, x, y, width, height); 37 | } 38 | 39 | public PixmapRegion(Pixmap pixmap, PixmapRegion region) { 40 | setRegion(pixmap, region.x, region.y, region.width, region.height); 41 | } 42 | 43 | private void changePixmap(Pixmap pixmap) { 44 | this.pixmap = pixmap; 45 | pixels = pixmap.getPixels(); 46 | pixelStride = PixmapUtils.getPixelStride(pixmap.getFormat()); 47 | lineStride = pixmap.getWidth() * pixelStride; 48 | } 49 | 50 | public Pixmap getPixmap() { 51 | return pixmap; 52 | } 53 | 54 | public int getPixel(int x, int y) { 55 | if (pixelStride != 4) { 56 | throw new GdxRuntimeException("Unsupported format"); 57 | } 58 | if (x < 0 || x >= width || y < 0 || y >= height) { 59 | throw new GdxRuntimeException("Out of bounds"); 60 | } 61 | int offset = (this.y + y) * lineStride + (this.x + x) * pixelStride; 62 | return pixels.getInt(offset); 63 | } 64 | 65 | public int getPixel(int x, int y, boolean flipX, boolean flipY) { 66 | return getPixel( 67 | flipX ? width - 1 - x : x, 68 | flipY ? height - 1 - y : y); 69 | } 70 | 71 | public int getPixelSlow(int x, int y) { 72 | if (pixelStride < 3) { 73 | throw new GdxRuntimeException("Unsupported format"); 74 | } 75 | 76 | int offset = (this.y + y) * lineStride + (this.x + x) * pixelStride; 77 | int r = Byte.toUnsignedInt(pixels.get(offset)); 78 | int g = Byte.toUnsignedInt(pixels.get(offset + 1)); 79 | int b = Byte.toUnsignedInt(pixels.get(offset + 2)); 80 | 81 | int a; 82 | if (pixelStride < 4) { 83 | a = (r + g + b) >= 4 ? 0xff : 0; 84 | } else { 85 | a = pixels.get(offset + 3); 86 | } 87 | 88 | return (r << 24) | (g << 16) | (b << 8) | a; 89 | } 90 | 91 | public void drawPixel(int x, int y, int color) { 92 | if (pixelStride != 4) { 93 | throw new GdxRuntimeException("Unsupported format"); 94 | } 95 | int offset = (this.y + y) * lineStride + (this.x + x) * pixelStride; 96 | pixels.putInt(offset, color); 97 | } 98 | 99 | public void setRegion(PixmapRegion region) { 100 | changePixmap(region.pixmap); 101 | this.x = region.x; 102 | this.y = region.y; 103 | this.width = region.width; 104 | this.height = region.height; 105 | } 106 | 107 | public void setRegion(Pixmap pixmap, int x, int y, int width, int height) { 108 | changePixmap(pixmap); 109 | this.x = x; 110 | this.y = y; 111 | this.width = width; 112 | this.height = height; 113 | } 114 | 115 | public int getRegionX() { 116 | return x; 117 | } 118 | 119 | public int getRegionY() { 120 | return y; 121 | } 122 | 123 | public int getRegionWidth() { 124 | return width; 125 | } 126 | 127 | public int getRegionHeight() { 128 | return height; 129 | } 130 | 131 | /** 132 | * Copies region UV data into {@link TextureRegion} instance. 133 | *

134 | * Does not modify {@link TextureRegion#texture}. 135 | */ 136 | public void getRegion(TextureRegion region) { 137 | 138 | float invTexWidth = 1.0f / pixmap.getWidth(); 139 | float invTexHeight = 1.0f / pixmap.getHeight(); 140 | 141 | region.u = x * invTexWidth; 142 | region.u2 = (x + width) * invTexWidth; 143 | 144 | region.v = y * invTexHeight; 145 | region.v2 = (y + height) * invTexHeight; 146 | 147 | region.regionWidth = width; 148 | region.regionHeight = height; 149 | } 150 | 151 | public Pixmap.Format getFormat() { 152 | return pixmap.getFormat(); 153 | } 154 | 155 | @Override 156 | public boolean equals(Object other) { 157 | 158 | if (other instanceof PixmapRegion) { 159 | 160 | PixmapRegion region = (PixmapRegion) other; 161 | 162 | return (this.pixmap == region.pixmap) 163 | && (this.x == region.x) && (this.y == region.y) 164 | && (this.width == region.width) && (this.height == region.height); 165 | } 166 | 167 | return false; 168 | } 169 | 170 | @Override 171 | public int hashCode() { 172 | assert false : "not implemented"; 173 | return -1; 174 | } 175 | 176 | } 177 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/graphics/g2d/PixmapTransform.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.graphics.g2d; 2 | 3 | import com.badlogic.gdx.graphics.Pixmap; 4 | 5 | public class PixmapTransform { 6 | 7 | public enum Mirror { 8 | None, 9 | X, 10 | Y, 11 | XY 12 | } 13 | 14 | public enum Rotate { 15 | None, 16 | _90, 17 | _180, 18 | _270 19 | } 20 | 21 | /** 22 | * Read pixel from {@link Pixmap}, using back-transformed flipped and rotated pixel coordinates. 23 | */ 24 | public static int getPixel(Pixmap pixmap, int x, int y, Mirror mirror, Rotate rotate) { 25 | 26 | int widthMinusOne = pixmap.getWidth() - 1; 27 | int heightMinusOne = pixmap.getHeight() - 1; 28 | 29 | int px = x, py = y; 30 | int outX, outY; 31 | 32 | // mirror 33 | 34 | if (mirror == Mirror.X || mirror == Mirror.XY) { 35 | px = widthMinusOne - x; 36 | } 37 | 38 | if (mirror == Mirror.Y || mirror == Mirror.XY) { 39 | py = heightMinusOne - y; 40 | } 41 | 42 | // rotate 43 | 44 | switch (rotate) { 45 | case _90: 46 | outX = py; 47 | outY = heightMinusOne - px; 48 | break; 49 | case _180: 50 | outX = widthMinusOne - px; 51 | outY = heightMinusOne - py; 52 | break; 53 | case _270: 54 | outX = widthMinusOne - py; 55 | outY = px; 56 | break; 57 | default: 58 | case None: 59 | outX = px; 60 | outY = py; 61 | break; 62 | } 63 | 64 | return pixmap.getPixel(outX, outY); 65 | } 66 | 67 | public static int getPixelRotated(Pixmap pixmap, int x, int y, Rotate rotate) { 68 | int widthMinusOne = pixmap.getWidth() - 1; 69 | int heightMinusOne = pixmap.getHeight() - 1; 70 | 71 | int px = x, py = y; 72 | int outX, outY; 73 | 74 | 75 | 76 | // rotate 77 | 78 | switch (rotate) { 79 | case _90: 80 | outX = py; 81 | outY = heightMinusOne - px; 82 | break; 83 | case _180: 84 | outX = widthMinusOne - px; 85 | outY = heightMinusOne - py; 86 | break; 87 | case _270: 88 | outX = widthMinusOne - py; 89 | outY = px; 90 | break; 91 | default: 92 | case None: 93 | outX = px; 94 | outY = py; 95 | break; 96 | } 97 | 98 | return pixmap.getPixel(outX, outY); 99 | } 100 | 101 | public static int getPixelMirrored(Pixmap pixmap, int x, int y, Mirror mirror) { 102 | int widthMinusOne = pixmap.getWidth() - 1; 103 | int heightMinusOne = pixmap.getHeight() - 1; 104 | 105 | int px = x, py = y; 106 | 107 | if (mirror == Mirror.X || mirror == Mirror.XY) { 108 | px = widthMinusOne - x; 109 | } 110 | 111 | if (mirror == Mirror.Y || mirror == Mirror.XY) { 112 | py = heightMinusOne - y; 113 | } 114 | 115 | return pixmap.getPixel(px, py); 116 | } 117 | 118 | public static Pixmap getRotatedMirroredCopy(Pixmap pixmap, Mirror mirror, Rotate rotate){ 119 | 120 | Pixmap mirrored = new Pixmap(pixmap.getWidth(), pixmap.getHeight(), pixmap.getFormat()); 121 | copyToMirrored(pixmap, mirrored, mirror); 122 | 123 | int newWidth = PixmapTransform.getWidth(pixmap, rotate); 124 | int newHeight = PixmapTransform.getHeight(pixmap, rotate); 125 | 126 | Pixmap copy = new Pixmap(newWidth, newHeight, pixmap.getFormat()); 127 | copyToRotated(mirrored, copy, rotate); 128 | 129 | /** Only temp for copying around */ 130 | mirrored.dispose(); 131 | 132 | return copy; 133 | } 134 | 135 | public static void copyToRotated(Pixmap source, Pixmap target, Rotate rotate){ 136 | for(int y=0; y> 16) | (masks[i] & 0xff00) | ((masks[i] & 0xff) << 16); 46 | } 47 | 48 | int a = MathUtils.floor(alpha * 255.0f) & 0xff; 49 | 50 | while (pixels.remaining() > 0) { 51 | 52 | int rgba = pixels.getInt(); 53 | int rgb = rgba >>> 8; 54 | 55 | for (int mask : masks) { 56 | if (rgb == mask) { 57 | pixels.position(pixels.position() - 4); 58 | pixels.putInt((rgba & 0xffffff00) | a); 59 | break; 60 | } 61 | } 62 | } 63 | 64 | pixels.flip(); 65 | } 66 | 67 | /** 68 | * Sets alpha for all pixels passing the RGB predicate function. 69 | */ 70 | public static void mask(Pixmap pixmap, float alpha, IntPredicate rgb) { 71 | 72 | ByteBuffer pixels = pixmap.getPixels(); 73 | 74 | int a = MathUtils.floor(alpha * 255.0f) & 0xff; 75 | 76 | while (pixels.remaining() > 0) { 77 | 78 | int rgba = pixels.getInt(); 79 | 80 | if (rgb.test(rgba >>> 8)) { 81 | pixels.position(pixels.position() - 4); 82 | pixels.putInt((rgba & 0xffffff00) | a); 83 | } 84 | } 85 | 86 | pixels.flip(); 87 | } 88 | 89 | public interface CropResult { 90 | 91 | void accept(int left, int bottom, int width, int height); 92 | } 93 | 94 | /** 95 | * Calculates crop regions of the pixmap in up to four directions. Pixel rows/columns are subject 96 | * to removal if all their pixels have an alpha channel value of exact the same value as given in the parameter. 97 | */ 98 | public static void crop(Pixmap pixmap, 99 | boolean left, 100 | boolean bottom, 101 | boolean right, 102 | boolean top, 103 | float alpha, 104 | CropResult consumer) { 105 | 106 | int width = pixmap.getWidth(); 107 | int height = pixmap.getHeight(); 108 | 109 | int a = MathUtils.floor(alpha * 255.0f) & 0xff; 110 | 111 | int minX = left ? width - 1 : 0; 112 | int maxX = right ? 0 : width - 1; 113 | 114 | int minY = bottom ? height - 1 : 0; 115 | int maxY = top ? 0 : height - 1; 116 | 117 | ByteBuffer pixels = pixmap.getPixels(); 118 | 119 | for (int y = 0; y < height; y++) { 120 | for (int x = 0; x < width; x++) { 121 | 122 | int rgba = pixels.getInt(); 123 | 124 | if ((rgba & 0xff) != a) { 125 | 126 | minX = Math.min(x, minX); 127 | maxX = Math.max(x, maxX); 128 | 129 | minY = Math.min(y, minY); 130 | maxY = Math.max(y, maxY); 131 | } 132 | } 133 | } 134 | 135 | pixels.flip(); 136 | 137 | consumer.accept(minX, minY, maxX, maxY); 138 | } 139 | 140 | /** 141 | * Returns a copy of the given pixmap, cropped about the given dimensions. 142 | */ 143 | public static Pixmap crop(Pixmap pixmap, int left, int bottom, int width, int height) { 144 | 145 | Pixmap result = new Pixmap(width, height, pixmap.getFormat()); 146 | result.drawPixmap(pixmap, 0, 0, left, bottom, width, height); 147 | 148 | return result; 149 | } 150 | 151 | /** 152 | * Horizontally mirrors the {@link Pixmap} content, in place, line by line. 153 | */ 154 | public static void flipX(Pixmap pixmap) { 155 | 156 | int width = pixmap.getWidth(); 157 | int height = pixmap.getHeight(); 158 | 159 | int bytesPerPixel = getPixelStride(pixmap.getFormat()); 160 | int pitch = width * bytesPerPixel; 161 | 162 | ByteBuffer pixels = pixmap.getPixels(); 163 | 164 | byte[] buffer = new byte[pitch]; 165 | 166 | for (int y = 0; y < height; y++) { 167 | 168 | pixels.position(y * pitch); 169 | pixels.get(buffer, 0, pitch); 170 | 171 | pixels.position(y * pitch); 172 | 173 | for (int x = 0, offs = pitch - bytesPerPixel; x < width; x++, offs -= bytesPerPixel) { 174 | pixels.put(buffer, offs, bytesPerPixel); 175 | } 176 | } 177 | 178 | pixels.position(0); 179 | } 180 | 181 | /** 182 | * Vertically mirrors the {@link Pixmap} content, in place, line by line. 183 | */ 184 | public static void flipY(Pixmap pixmap) { 185 | 186 | int width = pixmap.getWidth(); 187 | int height = pixmap.getHeight(); 188 | 189 | int pitch = width * getPixelStride(pixmap.getFormat()); 190 | 191 | ByteBuffer pixels = pixmap.getPixels(); 192 | 193 | byte[][] buffer = new byte[2][pitch]; 194 | 195 | for (int y = 0; y < height / 2; y++) { 196 | 197 | pixels.position(y * pitch); 198 | pixels.get(buffer[0], 0, pitch); 199 | 200 | pixels.position((height - y - 1) * pitch); 201 | pixels.get(buffer[1], 0, pitch); 202 | 203 | pixels.position(y * pitch); 204 | pixels.put(buffer[1], 0, pitch); 205 | 206 | pixels.position((height - y - 1) * pitch); 207 | pixels.put(buffer[0], 0, pitch); 208 | } 209 | 210 | pixels.position(0); 211 | } 212 | 213 | public static int getPixelStride(Format format) { 214 | switch (format) { 215 | case Alpha: 216 | case Intensity: 217 | return 1; 218 | case LuminanceAlpha: 219 | case RGB565: 220 | case RGBA4444: 221 | return 2; 222 | case RGB888: 223 | return 3; 224 | case RGBA8888: 225 | return 4; 226 | } 227 | throw new IllegalArgumentException("Not implemented"); 228 | } 229 | 230 | } 231 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/graphics/glutils/GLBufferObject.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.graphics.glutils; 2 | 3 | import com.badlogic.gdx.utils.BufferUtils; 4 | import com.badlogic.gdx.utils.Disposable; 5 | 6 | import java.nio.Buffer; 7 | import java.nio.ByteBuffer; 8 | 9 | import static com.badlogic.gdx.Gdx.gl20; 10 | import static com.badlogic.gdx.graphics.GL20.GL_DYNAMIC_DRAW; 11 | import static com.badlogic.gdx.graphics.GL20.GL_STATIC_DRAW; 12 | 13 | /** 14 | * Abstract base class to handle OpenGL buffer objects. 15 | */ 16 | abstract class GLBufferObject implements Disposable { 17 | 18 | private final int handle; 19 | private final int target; 20 | private final int usage; 21 | protected final int elementSize; 22 | private final ByteBuffer byteBuffer; 23 | 24 | protected T buffer; 25 | protected int wordSize; 26 | 27 | GLBufferObject(int target, boolean isStatic, int numElements, int elementSize) { 28 | 29 | handle = gl20.glGenBuffer(); 30 | 31 | this.target = target; 32 | usage = isStatic ? GL_STATIC_DRAW : GL_DYNAMIC_DRAW; 33 | 34 | this.elementSize = elementSize; 35 | byteBuffer = BufferUtils.newUnsafeByteBuffer(numElements * elementSize); 36 | 37 | createElementBuffer(byteBuffer); 38 | 39 | byteBuffer.flip(); 40 | } 41 | 42 | @Override 43 | public void dispose() { 44 | gl20.glDeleteBuffer(handle); 45 | BufferUtils.disposeUnsafeByteBuffer(byteBuffer); 46 | } 47 | 48 | public void bind() { 49 | gl20.glBindBuffer(target, handle); 50 | } 51 | 52 | public void unbind() { 53 | gl20.glBindBuffer(target, 0); 54 | } 55 | 56 | public void uploadData() { 57 | buffer.flip(); 58 | byteBuffer.limit(buffer.limit() * wordSize); 59 | gl20.glBufferData(target, byteBuffer.limit(), byteBuffer, usage); 60 | } 61 | 62 | /* TODO: proper API to specify buffer slice 63 | public void uploadSubData(int firstElement, int numElements) { 64 | gl20.glBufferSubData(target, firstElement * elementSize, numElements * elementSize, byteBuffer); 65 | }*/ 66 | 67 | public int getNumElements() { 68 | return buffer.limit() * wordSize / elementSize; 69 | } 70 | 71 | public int getMaxElements() { 72 | return byteBuffer.capacity() / elementSize; 73 | } 74 | 75 | public T getBuffer() { 76 | return buffer; 77 | } 78 | 79 | protected abstract void createElementBuffer(ByteBuffer byteBuffer); 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/graphics/glutils/GLTextureUtils.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.graphics.glutils; 2 | 3 | import com.badlogic.gdx.graphics.GL30; 4 | import com.badlogic.gdx.utils.BufferUtils; 5 | 6 | import java.nio.LongBuffer; 7 | 8 | import static com.badlogic.gdx.graphics.GL33Ext.*; 9 | 10 | public class GLTextureUtils { 11 | 12 | private static LongBuffer tmp = BufferUtils.newLongBuffer(1); 13 | 14 | public static boolean isInternalFormatSupported(int target, int format) { 15 | tmp.clear(); 16 | glGetInternalFormativ(target, format, GL_INTERNALFORMAT_SUPPORTED, tmp); 17 | return tmp.get(0) == GL30.GL_TRUE; 18 | } 19 | 20 | public static long getInternalFormatPreferred(int target, int format) { 21 | tmp.clear(); 22 | glGetInternalFormativ(target, format, GL_INTERNALFORMAT_PREFERRED, tmp); 23 | return tmp.get(0); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/graphics/glutils/IndexBufferObjectExt.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.graphics.glutils; 2 | 3 | import com.badlogic.gdx.graphics.GL33Ext; 4 | import com.badlogic.gdx.utils.BufferUtils; 5 | 6 | import java.nio.ByteBuffer; 7 | import java.nio.ShortBuffer; 8 | 9 | import static com.badlogic.gdx.Gdx.gl30; 10 | import static com.badlogic.gdx.graphics.GL30.*; 11 | 12 | /** 13 | * A lean replacement for {@link IndexBufferObject}. 14 | * Designed to work with {@link VertexArrayObject}. 15 | */ 16 | public class IndexBufferObjectExt extends GLBufferObject { 17 | 18 | public IndexBufferObjectExt(boolean isStatic, int numIndices) { 19 | super(GL_ELEMENT_ARRAY_BUFFER, isStatic, numIndices, 2); 20 | } 21 | 22 | public void setIndices(short[] indices, int offset, int count) { 23 | buffer.position(0); 24 | BufferUtils.copy(indices, offset, buffer, count); 25 | buffer.position(buffer.limit()); 26 | buffer.limit(buffer.capacity()); 27 | } 28 | 29 | public void addIndices(int count, short... indices) { 30 | buffer.put(indices, 0, count); 31 | } 32 | 33 | public void drawElements(int count, int offset) { 34 | gl30.glDrawElements(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, offset * wordSize); 35 | } 36 | 37 | public void drawElementsBaseVertex(int count, int offset, int baseVertex) { 38 | GL33Ext.glDrawElementsBaseVertex(GL_TRIANGLES, count, GL_UNSIGNED_SHORT, offset * wordSize, baseVertex); 39 | } 40 | 41 | @Override 42 | protected void createElementBuffer(ByteBuffer byteBuffer) { 43 | buffer = byteBuffer.asShortBuffer(); 44 | wordSize = 2; 45 | } 46 | 47 | public static boolean fitsElements(IndexBufferObjectExt bufferObject, int numElements) { 48 | return (bufferObject != null) && (numElements <= bufferObject.getMaxElements()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/graphics/glutils/MultiTargetFrameBuffer.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.graphics.glutils; 2 | 3 | import com.badlogic.gdx.graphics.*; 4 | import com.badlogic.gdx.graphics.glutils.GLFrameBuffer.FrameBufferBuilder; 5 | import com.badlogic.gdx.utils.*; 6 | 7 | import java.nio.*; 8 | 9 | import static com.badlogic.gdx.Gdx.gl30; 10 | import static com.badlogic.gdx.graphics.GL30.*; 11 | import static com.badlogic.gdx.graphics.GL33Ext.GL_CLAMP_TO_BORDER; 12 | import static com.badlogic.gdx.graphics.GL33Ext.GL_TEXTURE_BORDER_COLOR; 13 | 14 | /** 15 | * An extension to {@link FrameBuffer} with multiple color attachments. Can be used as 16 | * multi-render-target in deferred rendering (G-buffer). 17 | *

18 | * Uses alternate depth/stencil buffer formats to allow for GL_DEPTH24_STENCIL8. 19 | */ 20 | public class MultiTargetFrameBuffer implements Disposable { 21 | 22 | public enum Format { 23 | 24 | R32F(GL_R32F, GL_RED, GL_FLOAT), 25 | RG32F(GL_RG32F, GL_RG, GL_FLOAT), 26 | RGB32F(GL_RGB32F, GL_RGB, GL_FLOAT), 27 | RGBA32F(GL_RGBA32F, GL_RGBA, GL_FLOAT), 28 | 29 | RG16F(GL_RG16F, GL_RG, GL_HALF_FLOAT), 30 | 31 | R8(GL_R8, GL_RED, GL_UNSIGNED_BYTE), 32 | RG8(GL_RG8, GL_RG, GL_UNSIGNED_BYTE), 33 | 34 | R16I(GL_R16I, GL_RED_INTEGER, GL_SHORT), 35 | R32I(GL_R32I, GL_RED_INTEGER, GL_INT), 36 | 37 | PixmapFormat(GL_NONE, GL_NONE, GL_NONE); 38 | 39 | private final int internal, format, type; 40 | 41 | Format(int internal, int format, int type) { 42 | this.internal = internal; 43 | this.format = format; 44 | this.type = type; 45 | } 46 | } 47 | 48 | public static class ColorAttachmentFormat { 49 | 50 | Format format = Format.PixmapFormat; 51 | Pixmap.Format pixmapFormat = Pixmap.Format.RGB888; 52 | boolean generateMipmaps = false; 53 | Texture.TextureFilter minFilter = Texture.TextureFilter.Nearest; 54 | Texture.TextureFilter magFilter = Texture.TextureFilter.Nearest; 55 | Texture.TextureWrap wrap = Texture.TextureWrap.ClampToEdge; 56 | 57 | public ColorAttachmentFormat(Format format, 58 | Pixmap.Format pixmapFormat) { 59 | this.format = format; 60 | this.pixmapFormat = pixmapFormat; 61 | } 62 | 63 | public ColorAttachmentFormat(Format format, 64 | Pixmap.Format pixmapFormat, 65 | boolean generateMipmaps, 66 | Texture.TextureFilter minFilter, 67 | Texture.TextureFilter magFilter) { 68 | this.format = format; 69 | this.pixmapFormat = pixmapFormat; 70 | this.generateMipmaps = generateMipmaps; 71 | this.minFilter = minFilter; 72 | this.magFilter = magFilter; 73 | } 74 | 75 | public ColorAttachmentFormat(Format format, 76 | Pixmap.Format pixmapFormat, 77 | boolean generateMipmaps, 78 | Texture.TextureFilter minFilter, 79 | Texture.TextureFilter magFilter, 80 | Texture.TextureWrap wrap) { 81 | this.format = format; 82 | this.pixmapFormat = pixmapFormat; 83 | this.generateMipmaps = generateMipmaps; 84 | this.minFilter = minFilter; 85 | this.magFilter = magFilter; 86 | this.wrap = wrap; 87 | } 88 | } 89 | 90 | private final FrameBuffer frameBuffer; 91 | private final Texture[] colorTextures; 92 | private final boolean hasStencil; 93 | 94 | private static final FloatBuffer tmpColors = BufferUtils.newFloatBuffer(4); 95 | 96 | /** 97 | * Creates a new MRT FrameBuffer with the given color buffer format and dimensions. 98 | */ 99 | public static MultiTargetFrameBuffer create(Format format, int numColorBuffers, 100 | int width, int height, boolean hasDepth, boolean hasStencil) { 101 | 102 | return create(format, null, numColorBuffers, width, height, hasDepth, hasStencil); 103 | } 104 | 105 | /** 106 | * Creates a new MRT FrameBuffer with the given {@link Pixmap} format and dimensions. 107 | */ 108 | public static MultiTargetFrameBuffer create(Pixmap.Format pixmapFormat, int numColorBuffers, 109 | int width, int height, boolean hasDepth, boolean hasStencil) { 110 | 111 | return create(Format.PixmapFormat, pixmapFormat, numColorBuffers, width, height, hasDepth, hasStencil); 112 | } 113 | 114 | /** 115 | * Creates a new MRT FrameBuffer with the given color buffer format and dimensions. If the format is 116 | * {@link Format#PixmapFormat}, the pixmapFormat parameter is used, otherwise it is ignored. 117 | */ 118 | public static MultiTargetFrameBuffer create(Format format, Pixmap.Format pixmapFormat, int numColorBuffers, 119 | int width, int height, boolean hasDepth, boolean hasStencil) { 120 | 121 | ColorAttachmentFormat[] fbCreateFormats = new ColorAttachmentFormat[numColorBuffers]; 122 | 123 | for (int i = 0; i < numColorBuffers; i++) { 124 | fbCreateFormats[i] = new ColorAttachmentFormat(format, pixmapFormat); 125 | } 126 | 127 | return create(fbCreateFormats, width, height, hasDepth, hasStencil); 128 | } 129 | 130 | /** 131 | * Creates a new MRT FrameBuffer with the given color buffer formats and dimensions. 132 | *

133 | * This function equals {@link MultiTargetFrameBuffer#create(Format, Pixmap.Format, int, int, int, boolean, boolean)} 134 | * but individually describes the format for each color buffer. 135 | */ 136 | public static MultiTargetFrameBuffer create(ColorAttachmentFormat[] formats, 137 | int width, 138 | int height, 139 | boolean hasDepth, 140 | boolean hasStencil) { 141 | 142 | FrameBufferBuilder builder = new FrameBufferBuilder(width, height); 143 | 144 | for (ColorAttachmentFormat attachment : formats) { 145 | 146 | if (attachment.format == Format.PixmapFormat) { 147 | builder.addBasicColorTextureAttachment(attachment.pixmapFormat); 148 | } else { 149 | builder.addColorTextureAttachment(attachment.format.internal, 150 | attachment.format.format, attachment.format.type); 151 | } 152 | } 153 | 154 | if (hasDepth && hasStencil) { 155 | builder.addBasicStencilDepthPackedRenderBuffer(); 156 | } else if (hasDepth) { 157 | builder.addDepthTextureAttachment(GL_DEPTH_COMPONENT32F, GL_FLOAT); 158 | } 159 | 160 | FrameBuffer frameBuffer = builder.build(); 161 | Array textures = frameBuffer.getTextureAttachments(); 162 | 163 | for (int i = 0; i < formats.length; i++) { 164 | 165 | ColorAttachmentFormat attachment = formats[i]; 166 | Texture texture = textures.get(i); 167 | 168 | texture.setFilter(attachment.minFilter, attachment.magFilter); 169 | texture.setWrap(attachment.wrap, attachment.wrap); 170 | } 171 | 172 | return new MultiTargetFrameBuffer(frameBuffer, hasDepth, hasStencil); 173 | } 174 | 175 | private MultiTargetFrameBuffer(FrameBuffer frameBuffer, boolean hasDepth, boolean hasStencil) { 176 | 177 | this.frameBuffer = frameBuffer; 178 | 179 | this.colorTextures = new Texture[frameBuffer.textureAttachments.size]; 180 | for (int i = 0; i < frameBuffer.textureAttachments.size; i++) { 181 | this.colorTextures[i] = frameBuffer.textureAttachments.get(i); 182 | } 183 | 184 | this.hasStencil = hasStencil; 185 | } 186 | 187 | @Override 188 | public void dispose() { 189 | frameBuffer.dispose(); 190 | } 191 | 192 | public int getWidth() { 193 | return frameBuffer.getWidth(); 194 | } 195 | 196 | public int getHeight() { 197 | return frameBuffer.getHeight(); 198 | } 199 | 200 | public void bind() { 201 | frameBuffer.bind(); 202 | } 203 | 204 | public void begin() { 205 | frameBuffer.begin(); 206 | } 207 | 208 | public void end() { 209 | frameBuffer.end(); 210 | } 211 | 212 | public Texture getColorBufferTexture(int index) { 213 | return colorTextures[index]; 214 | } 215 | 216 | public void clampToBorder(int index, Color color) { 217 | int handle = colorTextures[index].getTextureObjectHandle(); 218 | gl30.glBindTexture(GL_TEXTURE_2D, handle); 219 | 220 | gl30.glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); 221 | gl30.glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); 222 | 223 | synchronized (tmpColors) { 224 | tmpColors.clear(); 225 | tmpColors.put(color.r); 226 | tmpColors.put(color.g); 227 | tmpColors.put(color.b); 228 | tmpColors.put(color.a); 229 | tmpColors.flip(); 230 | 231 | gl30.glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, tmpColors); 232 | } 233 | 234 | gl30.glBindTexture(GL_TEXTURE_2D, 0); 235 | } 236 | 237 | public void generateMipmap(int index) { 238 | int handle = colorTextures[index].getTextureObjectHandle(); 239 | gl30.glBindTexture(GL_TEXTURE_2D, handle); 240 | gl30.glGenerateMipmap(GL_TEXTURE_2D); 241 | gl30.glBindTexture(GL_TEXTURE_2D, 0); 242 | } 243 | 244 | public void clearColorBuffer(Color color, int index) { 245 | clearColorBuffer(color.r, color.g, color.b, color.a, index); 246 | } 247 | 248 | public void clearColorBuffer(float r, float g, float b, float a, int index) { 249 | synchronized (tmpColors) { 250 | tmpColors.clear(); 251 | tmpColors.put(r); 252 | tmpColors.put(g); 253 | tmpColors.put(b); 254 | tmpColors.put(a); 255 | tmpColors.flip(); 256 | 257 | gl30.glClearBufferfv(GL_COLOR, index, tmpColors); 258 | } 259 | } 260 | 261 | public void clearColorBuffers(Color color) { 262 | clearColorBuffers(color.r, color.g, color.b, color.a); 263 | } 264 | 265 | public void clearColorBuffers(float r, float g, float b, float a) { 266 | synchronized (tmpColors) { 267 | tmpColors.clear(); 268 | tmpColors.put(r); 269 | tmpColors.put(g); 270 | tmpColors.put(b); 271 | tmpColors.put(a); 272 | tmpColors.flip(); 273 | 274 | for (int index = 0; index < colorTextures.length; index++) { 275 | gl30.glClearBufferfv(GL_COLOR, index, tmpColors); 276 | } 277 | } 278 | } 279 | 280 | public void clearColorBuffers(Color color, int[] indices) { 281 | clearColorBuffers(color.r, color.g, color.b, color.a, indices); 282 | } 283 | 284 | public void clearColorBuffers(float r, float g, float b, float a, int[] indices) { 285 | synchronized (tmpColors) { 286 | tmpColors.clear(); 287 | tmpColors.put(r); 288 | tmpColors.put(g); 289 | tmpColors.put(b); 290 | tmpColors.put(a); 291 | tmpColors.flip(); 292 | 293 | for (int index : indices) { 294 | gl30.glClearBufferfv(GL_COLOR, index, tmpColors); 295 | } 296 | } 297 | } 298 | 299 | public void clearDepthBuffer(float depth) { 300 | synchronized (tmpColors) { 301 | tmpColors.clear(); 302 | tmpColors.put(depth); 303 | tmpColors.flip(); 304 | 305 | gl30.glClearBufferfv(GL_DEPTH, 0, tmpColors); 306 | } 307 | } 308 | 309 | public void clearDepthStencilBuffer(float depth, int stencil) { 310 | gl30.glClearBufferfi(GL_DEPTH_STENCIL, 0, depth, stencil); 311 | } 312 | 313 | public static void readColorBuffer(Pixmap target, 314 | MultiTargetFrameBuffer source, int srcIndex, 315 | int srcX0, int srcY0, int srcX1, int srcY1) { 316 | 317 | gl30.glBindFramebuffer(GL_READ_FRAMEBUFFER, source.frameBuffer.getFramebufferHandle()); 318 | gl30.glReadBuffer(GL_COLOR_ATTACHMENT0 + srcIndex); 319 | 320 | gl30.glPixelStorei(GL_PACK_ALIGNMENT, 1); 321 | 322 | ByteBuffer pixels = target.getPixels(); 323 | 324 | int glFormat = target.getGLFormat(); 325 | gl30.glReadPixels(srcX0, srcY0, srcX1 - srcX0, srcY1 - srcY0, glFormat, GL_UNSIGNED_BYTE, pixels); 326 | 327 | gl30.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); 328 | gl30.glReadBuffer(GL_BACK); 329 | } 330 | 331 | public static void readFloatBuffer(FloatBuffer target, 332 | MultiTargetFrameBuffer source, int srcIndex, 333 | int srcX0, int srcY0, int srcX1, int srcY1) { 334 | 335 | gl30.glBindFramebuffer(GL_READ_FRAMEBUFFER, source.frameBuffer.getFramebufferHandle()); 336 | gl30.glReadBuffer(GL_COLOR_ATTACHMENT0 + srcIndex); 337 | 338 | gl30.glPixelStorei(GL_PACK_ALIGNMENT, 4); 339 | 340 | gl30.glReadPixels(srcX0, srcY0, srcX1 - srcX0, srcY1 - srcY0, GL_RED, GL_FLOAT, target); 341 | 342 | gl30.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); 343 | gl30.glReadBuffer(GL_BACK); 344 | } 345 | 346 | public static void copyDepthStencilBuffer(MultiTargetFrameBuffer target, 347 | int destX0, int destY0, int destX1, int destY1, 348 | MultiTargetFrameBuffer source, 349 | int srcX0, int srcY0, int srcX1, int srcY1) { 350 | 351 | int mask = GL_DEPTH_BUFFER_BIT; 352 | 353 | if (source.hasStencil && target.hasStencil) { 354 | mask |= GL_STENCIL_BUFFER_BIT; 355 | } 356 | 357 | int sourceFbo = source.frameBuffer.getFramebufferHandle(); 358 | int targetFbo = target.frameBuffer.getFramebufferHandle(); 359 | 360 | gl30.glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceFbo); 361 | gl30.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, targetFbo); 362 | 363 | gl30.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, destX0, destY0, destX1, destY1, mask, GL_NEAREST); 364 | 365 | gl30.glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); 366 | gl30.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); 367 | } 368 | 369 | } 370 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/graphics/glutils/ShaderProgramExt.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.graphics.glutils; 2 | 3 | import com.badlogic.gdx.function.Consumer; 4 | import com.badlogic.gdx.utils.Disposable; 5 | 6 | /** 7 | * Extension to {@link ShaderProgram} for customized shader construction. The "onCreate" consumer function 8 | * is called during shader creation, between calls to glCreateProgram() and glLinkProgram(). 9 | */ 10 | public class ShaderProgramExt implements Disposable { 11 | 12 | private int handle; 13 | private Program program; 14 | private Consumer onCreate; 15 | private Runnable onDispose; 16 | 17 | public ShaderProgramExt(String vertexShader, String fragmentShader, 18 | Consumer onCreate, Runnable onDispose) { 19 | 20 | this.onCreate = onCreate; 21 | this.onDispose = onDispose; 22 | this.program = new Program(vertexShader, fragmentShader); 23 | } 24 | 25 | @Override 26 | public void dispose() { 27 | program.dispose(); 28 | } 29 | 30 | public ShaderProgram getProgram() { 31 | return program; 32 | } 33 | 34 | private class Program extends ShaderProgram { 35 | 36 | public Program(String vertexShader, String fragmentShader) { 37 | super(vertexShader, fragmentShader); 38 | } 39 | 40 | @Override 41 | public void dispose() { 42 | onDispose.run(); 43 | super.dispose(); 44 | } 45 | 46 | @Override 47 | protected int createProgram() { 48 | handle = super.createProgram(); 49 | onCreate.accept(handle); 50 | return handle; 51 | } 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/graphics/glutils/VertexArrayObject.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.graphics.glutils; 2 | 3 | import com.badlogic.gdx.utils.Disposable; 4 | 5 | import static com.badlogic.gdx.Gdx.gl30; 6 | 7 | /** 8 | * Manages VAO bindings. 9 | */ 10 | public class VertexArrayObject implements Disposable { 11 | 12 | private final int[] vao = new int[1]; 13 | 14 | private VertexAttributeArray attributes; 15 | private int maxLocation = -1; 16 | 17 | public VertexArrayObject() { 18 | gl30.glGenVertexArrays(1, vao, 0); 19 | } 20 | 21 | @Override 22 | public void dispose() { 23 | gl30.glDeleteVertexArrays(1, vao, 0); 24 | } 25 | 26 | public void bind() { 27 | gl30.glBindVertexArray(vao[0]); 28 | } 29 | 30 | public void bindVertexLayout(VertexAttributeArray attributes) { 31 | 32 | int[] locations = new int[attributes.size()]; 33 | for (int l = 0; l < attributes.size(); l++) { 34 | locations[l] = l; 35 | } 36 | 37 | bindVertexLayout(locations, attributes); 38 | } 39 | 40 | public void bindVertexLayout(int[] locations, VertexAttributeArray attributes) { 41 | 42 | unbindLocations(); 43 | 44 | this.attributes = attributes; 45 | 46 | int count = Math.min(locations.length, attributes.size()); 47 | 48 | for (int i = 0; i < count; i++) { 49 | bindLocation(locations[i], i); 50 | } 51 | } 52 | 53 | private void bindLocation(int location, int attribute) { 54 | 55 | if (location < 0) { 56 | return; 57 | } 58 | 59 | gl30.glEnableVertexAttribArray(location); 60 | attributes.bind(location, attribute); 61 | 62 | maxLocation = Math.max(location, maxLocation); 63 | } 64 | 65 | private void unbindLocations() { 66 | 67 | for (int i = 0; i <= maxLocation; i++) { 68 | gl30.glDisableVertexAttribArray(i); 69 | } 70 | 71 | maxLocation = -1; 72 | } 73 | 74 | public static void unbind() { 75 | gl30.glBindVertexArray(0); 76 | } 77 | 78 | public VertexAttributeArray getAttributes() { 79 | return attributes; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/graphics/glutils/VertexAttributeArray.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.graphics.glutils; 2 | 3 | import com.badlogic.gdx.utils.GdxRuntimeException; 4 | 5 | import java.util.Arrays; 6 | 7 | import static com.badlogic.gdx.Gdx.gl30; 8 | import static com.badlogic.gdx.graphics.GL30.*; 9 | 10 | public class VertexAttributeArray { 11 | 12 | public static class Attribute { 13 | 14 | public final int numComponents; 15 | public final int type; 16 | public final boolean normalized; 17 | 18 | private final Alias alias; 19 | 20 | protected int offset; 21 | private final int stride; 22 | 23 | public Attribute(int numComponents, int type, boolean normalized, Alias alias) { 24 | this.numComponents = numComponents; 25 | this.type = type; 26 | this.normalized = normalized; 27 | this.alias = alias; 28 | // todo: 29 | this.stride = 0; 30 | } 31 | 32 | public int getOffset() { 33 | return offset; 34 | } 35 | 36 | void bind(int location, int stride) { 37 | if (type == GL_FLOAT || normalized) { 38 | gl30.glVertexAttribPointer(location, numComponents, type, normalized, stride, offset); 39 | } else { 40 | gl30.glVertexAttribIPointer(location, numComponents, type, stride, offset); 41 | } 42 | } 43 | 44 | } 45 | 46 | public enum Alias { 47 | 48 | Position("a_position"), 49 | Normal("a_normal"), 50 | ColorPacked("a_color"), 51 | 52 | TexCoord0("a_texCoord0"), 53 | 54 | Generic(null); 55 | 56 | Alias(String alias) { 57 | this.alias = alias; 58 | } 59 | 60 | private String alias; 61 | } 62 | 63 | private final Attribute[] attributes; 64 | 65 | private final int vertexSize; 66 | private final int vertexStride; 67 | 68 | public VertexAttributeArray(Attribute... attributes) { 69 | this(0, attributes); 70 | } 71 | 72 | public VertexAttributeArray(VertexAttributeArray other) { 73 | this(other.vertexStride, other.attributes); 74 | } 75 | 76 | public VertexAttributeArray(int vertexStride, Attribute... attributes) { 77 | 78 | this.attributes = Arrays.copyOf(attributes, attributes.length); 79 | 80 | this.vertexStride = vertexStride; 81 | this.vertexSize = calculateSizeAndOffsets(); 82 | } 83 | 84 | public int size() { 85 | return attributes.length; 86 | } 87 | 88 | public Attribute get(int index) { 89 | return attributes[index]; 90 | } 91 | 92 | public void bind(int location, int index) { 93 | int stride = vertexSize + vertexStride; 94 | //int stride = (vertexStride != 0) ? (vertexSize + vertexStride) : 0; 95 | attributes[index].bind(location, stride); 96 | } 97 | 98 | public Attribute find(Alias alias) { 99 | 100 | for (Attribute attribute : attributes) { 101 | if (attribute.alias == alias) { 102 | return attribute; 103 | } 104 | } 105 | 106 | return null; 107 | } 108 | 109 | public int getVertexSize() { 110 | return vertexSize + vertexStride; 111 | } 112 | 113 | private int calculateSizeAndOffsets() { 114 | 115 | int offset = 0; 116 | 117 | for (Attribute attribute : attributes) { 118 | 119 | attribute.offset = offset; 120 | 121 | offset += attribute.numComponents * getTypeSize(attribute.type); 122 | offset += attribute.stride; 123 | 124 | } 125 | 126 | return offset; 127 | } 128 | 129 | private int getTypeSize(int type) { 130 | 131 | switch (type) { 132 | 133 | case GL_BYTE: 134 | case GL_UNSIGNED_BYTE: 135 | return 1; 136 | 137 | case GL_SHORT: 138 | case GL_UNSIGNED_SHORT: 139 | return 2; 140 | 141 | case GL_INT: 142 | case GL_UNSIGNED_INT: 143 | case GL_FLOAT: 144 | return 4; 145 | 146 | default: 147 | throw new GdxRuntimeException("Unsupported vertex attribute type!"); 148 | } 149 | } 150 | 151 | public static Attribute Float(Alias alias, int numComponents, boolean normalized) { 152 | return new Attribute(numComponents, GL_FLOAT, normalized, alias); 153 | } 154 | 155 | public static Attribute UnsignedByte(Alias alias, int numComponents) { 156 | return new Attribute(numComponents, GL_UNSIGNED_BYTE, true, alias); 157 | } 158 | 159 | public static Attribute Int(Alias alias, int numComponents) { 160 | return new Attribute(numComponents, GL_INT, false, alias); 161 | } 162 | 163 | public static Attribute UnsignedInt(Alias alias, int numComponents) { 164 | return new Attribute(numComponents, GL_UNSIGNED_INT, false, alias); 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/graphics/glutils/VertexBufferObjectExt.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.graphics.glutils; 2 | 3 | import com.badlogic.gdx.utils.BufferUtils; 4 | 5 | import java.nio.ByteBuffer; 6 | import java.nio.FloatBuffer; 7 | 8 | import static com.badlogic.gdx.Gdx.gl30; 9 | import static com.badlogic.gdx.graphics.GL20.GL_TRIANGLES; 10 | import static com.badlogic.gdx.graphics.GL30.GL_ARRAY_BUFFER; 11 | 12 | /** 13 | * A lean replacement for {@link VertexBufferObjectWithVAO}. 14 | * Designed to work with {@link VertexArrayObject}. 15 | */ 16 | public class VertexBufferObjectExt extends GLBufferObject { 17 | 18 | public VertexBufferObjectExt(boolean isStatic, int numVertices, int vertexSize) { 19 | super(GL_ARRAY_BUFFER, isStatic, numVertices, vertexSize); 20 | } 21 | 22 | public void setVertices(float[] vertices, int offset, int count) { 23 | buffer.position(0); 24 | BufferUtils.copy(vertices, buffer, count, offset); 25 | buffer.position(buffer.limit()); 26 | buffer.limit(buffer.capacity()); 27 | } 28 | 29 | public void addVertices(int count, float... vertices) { 30 | buffer.put(vertices, 0, count); 31 | } 32 | 33 | public void drawArrays(int count, int offset) { 34 | gl30.glDrawArrays(GL_TRIANGLES, offset, count); 35 | } 36 | 37 | @Override 38 | protected void createElementBuffer(ByteBuffer byteBuffer) { 39 | buffer = byteBuffer.asFloatBuffer(); 40 | wordSize = 4; 41 | } 42 | 43 | public static boolean fitsElements(VertexBufferObjectExt bufferObject, int numElements, int elementSize) { 44 | return (bufferObject != null) 45 | && (numElements <= bufferObject.getMaxElements()) 46 | && (elementSize == bufferObject.elementSize); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/json/AnnotatedJson.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.json; 2 | 3 | import com.badlogic.gdx.files.FileHandle; 4 | import com.badlogic.gdx.function.Predicate; 5 | import com.badlogic.gdx.function.*; 6 | import com.badlogic.gdx.lang.ClassFinder; 7 | import com.badlogic.gdx.utils.*; 8 | 9 | import java.io.*; 10 | import java.nio.charset.StandardCharsets; 11 | import java.util.zip.GZIPInputStream; 12 | import java.util.zip.GZIPOutputStream; 13 | 14 | /** 15 | * Utility functions to read or write a hierarchy of objects annotated with 16 | * {@link com.badlogic.gdx.json.annotations.JsonSerializable} and {@link com.badlogic.gdx.json.annotations.JsonSerialize}. 17 | */ 18 | public class AnnotatedJson { 19 | 20 | public static Json newReader(Class clazz, Consumer setupJson) { 21 | 22 | Json json = new Json(); 23 | json.setSerializer(clazz, new AnnotatedJsonSerializer<>(json, clazz)); 24 | 25 | if (setupJson != null) { 26 | setupJson.accept(json); 27 | } 28 | 29 | return json; 30 | } 31 | 32 | public static T read(FileHandle path, Class clazz, Consumer setupJson) throws IOException { 33 | Json json = newReader(clazz, setupJson); 34 | return read(path, clazz, json); 35 | } 36 | 37 | public static T read(FileHandle path, Class clazz, Json json) throws IOException { 38 | try { 39 | InputStream fileStream = path.read(); 40 | BufferedInputStream stream = new BufferedInputStream(fileStream); 41 | Reader reader = new InputStreamReader(stream, StandardCharsets.UTF_8); 42 | return json.fromJson(clazz, reader); 43 | } catch (SerializationException e) { 44 | GdxSnippets.log.error("Error while serializing class " + clazz.getName(), e); 45 | throw new IOException(e.getCause()); 46 | } catch (RuntimeException e) { 47 | throw new IOException(e); 48 | } 49 | } 50 | 51 | public static T read(byte[] bytes, Class clazz, Json json) throws IOException { 52 | try { 53 | InputStream bais = new ByteArrayInputStream(bytes); 54 | Reader reader = new InputStreamReader(bais, StandardCharsets.UTF_8); 55 | return json.fromJson(clazz, reader); 56 | } catch (SerializationException e) { 57 | GdxSnippets.log.error("Error while serializing class " + clazz.getName(), e); 58 | throw new IOException(e.getCause()); 59 | } catch (RuntimeException e) { 60 | throw new IOException(e); 61 | } 62 | } 63 | 64 | public static T readGZip(FileHandle path, Class clazz, Json json) throws IOException { 65 | try { 66 | InputStream fileStream = path.read(); 67 | InputStream stream = new GZIPInputStream(fileStream); 68 | Reader reader = new InputStreamReader(stream, StandardCharsets.UTF_8); 69 | return json.fromJson(clazz, reader); 70 | } catch (SerializationException e) { 71 | GdxSnippets.log.error("Error while serializing class " + clazz.getName(), e); 72 | throw new IOException(e.getCause()); 73 | } catch (RuntimeException e) { 74 | throw new IOException(e); 75 | } 76 | } 77 | 78 | public static T readGZip(byte[] bytes, Class clazz, Json json) throws IOException { 79 | try { 80 | InputStream bais = new ByteArrayInputStream(bytes); 81 | InputStream gzip = new GZIPInputStream(bais); 82 | Reader reader = new InputStreamReader(gzip, StandardCharsets.UTF_8); 83 | return json.fromJson(clazz, reader); 84 | } catch (SerializationException e) { 85 | GdxSnippets.log.error("Error while serializing class " + clazz.getName(), e); 86 | throw new IOException(e.getCause()); 87 | } catch (RuntimeException e) { 88 | throw new IOException(e); 89 | } 90 | } 91 | 92 | public static Json newWriter(Class clazz, Consumer setupJson) { 93 | 94 | Json json = new Json(JsonWriter.OutputType.json); 95 | json.setSerializer(clazz, new AnnotatedJsonSerializer<>(json, clazz)); 96 | 97 | if (setupJson != null) { 98 | setupJson.accept(json); 99 | } 100 | 101 | return json; 102 | } 103 | 104 | public static void write(FileHandle path, T object, Class clazz, Consumer setupJson) throws IOException { 105 | Json json = newWriter(clazz, setupJson); 106 | write(path, false, object, json); 107 | } 108 | 109 | public static void write(FileHandle path, boolean compact, T object, Class clazz, Consumer setupJson) throws IOException { 110 | Json json = newWriter(clazz, setupJson); 111 | write(path, compact, object, json); 112 | } 113 | 114 | public static void write(FileHandle path, T object, Json json) throws IOException { 115 | write(path, false, object, json); 116 | } 117 | 118 | public static void write(FileHandle path, boolean compact, T object, Json json) throws IOException { 119 | 120 | String output = json.toJson(object); 121 | String prettyOutput = compact ? output : json.prettyPrint(output); 122 | 123 | try (FileOutputStream fos = new FileOutputStream(path.file(), false)) { 124 | try (Writer writer = new OutputStreamWriter(fos, StandardCharsets.UTF_8)) { 125 | writer.write(prettyOutput); 126 | writer.flush(); 127 | } 128 | } 129 | } 130 | 131 | public static byte[] write(T object, Json json) throws IOException { 132 | 133 | ByteArrayOutputStream baos = new ByteArrayOutputStream(65536); 134 | 135 | try (Writer writer = new OutputStreamWriter(baos, StandardCharsets.UTF_8)) { 136 | json.toJson(object, writer); 137 | return baos.toByteArray(); 138 | } 139 | } 140 | 141 | public static void writeGZip(FileHandle path, boolean compact, T object, Json json) throws IOException { 142 | 143 | String output = json.toJson(object); 144 | String prettyOutput = compact ? output : json.prettyPrint(output); 145 | 146 | try (OutputStream gzip = new GZIPOutputStream(new FileOutputStream(path.file(), false), true)) { 147 | try (Writer writer = new OutputStreamWriter(gzip, StandardCharsets.UTF_8)) { 148 | writer.write(prettyOutput); 149 | writer.flush(); 150 | } 151 | } 152 | } 153 | 154 | public static byte[] writeGZip(T object, Json json) throws IOException { 155 | 156 | ByteArrayOutputStream baos = new ByteArrayOutputStream(65536); 157 | 158 | try (OutputStream gzip = new GZIPOutputStream(baos, true)) { 159 | try (Writer writer = new OutputStreamWriter(gzip, StandardCharsets.UTF_8)) { 160 | json.toJson(object, writer); 161 | return baos.toByteArray(); 162 | } 163 | } 164 | } 165 | 166 | /** 167 | * Convenience function to register another annotated Json serializer. 168 | */ 169 | public static void register(Json json, Class clazz) { 170 | json.setSerializer(clazz, new AnnotatedJsonSerializer<>(json, clazz)); 171 | } 172 | 173 | /** 174 | * Convenience function to register another Json serializer. 175 | */ 176 | public static void register(Json json, Class clazz, Json.Serializer serializer) { 177 | json.setSerializer(clazz, serializer); 178 | } 179 | 180 | /** 181 | * Scans for subclasses of the given class, and adds annotated serializers for each 182 | * of them. Only classes located the same URL as the given class are searched. 183 | */ 184 | @Deprecated 185 | public static void registerSubclasses(Json json, Class clazz, 186 | Predicate clazzNameFilter) { 187 | 188 | if (clazzNameFilter == null) { 189 | clazzNameFilter = (name) -> true; 190 | } 191 | 192 | new ClassFinder() 193 | .filterURLforClass(clazz) 194 | .process(clazzNameFilter, aClazz -> { 195 | if (!clazz.equals(aClazz) && clazz.isAssignableFrom(aClazz)) { 196 | register(json, aClazz.asSubclass(clazz)); 197 | } 198 | }); 199 | } 200 | 201 | public static void enumerateAllClasses(Array allClassNames, 202 | Class referenceClass, 203 | Predicate classNameFilter) { 204 | 205 | new ClassFinder() 206 | .filterURLforClass(referenceClass) 207 | .process( 208 | name -> { 209 | if (classNameFilter.test(name)) { 210 | allClassNames.add(name); 211 | } 212 | return false; 213 | }, 214 | clazz -> { 215 | }); 216 | } 217 | 218 | public static void registerSubclasses(Json json, Class clazz, 219 | Array allClassNames, 220 | Predicate classNameFilter) { 221 | 222 | Predicate filter = classNameFilter != null ? classNameFilter : (name -> true); 223 | 224 | for (int i = 0; i < allClassNames.size; i++) { 225 | 226 | String name = allClassNames.get(i); 227 | 228 | if (filter.test(name)) { 229 | try { 230 | Class aClazz = Class.forName(name); 231 | if (!clazz.equals(aClazz) && clazz.isAssignableFrom(aClazz)) { 232 | register(json, aClazz.asSubclass(clazz)); 233 | } 234 | } catch (ClassNotFoundException ignored) { 235 | 236 | } 237 | } 238 | } 239 | } 240 | 241 | } 242 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/json/AnnotatedJsonObject.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.json; 2 | 3 | /** 4 | * Optional interface to customize JSON serialization. Classes annotated with 5 | * {@link com.badlogic.gdx.json.annotations.JsonSerializable} can implement this 6 | * interface to hook into the serialization process. 7 | */ 8 | public interface AnnotatedJsonObject { 9 | 10 | /** 11 | * This function is called before writing the annotated object. 12 | */ 13 | void onJsonWrite(); 14 | 15 | /** 16 | * This function is called after reading the annotated object. 17 | */ 18 | void onJsonRead(); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/json/JsonArraySerializer.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.json; 2 | 3 | import com.badlogic.gdx.json.annotations.JsonArray; 4 | import com.badlogic.gdx.utils.*; 5 | import com.badlogic.gdx.utils.reflect.ArrayReflection; 6 | 7 | /** 8 | * Implementation of {@link com.badlogic.gdx.utils.Json.Serializer} to serialize {@link Array} containers. 9 | *

10 | * This is used internally by {@link AnnotatedJsonSerializer}. 11 | */ 12 | class JsonArraySerializer implements Json.Serializer> { 13 | 14 | private String name; 15 | private JsonArray array; 16 | 17 | JsonArraySerializer(String name, JsonArray array) { 18 | this.name = name; 19 | this.array = array; 20 | } 21 | 22 | @Override 23 | public void write(Json json, Array object, Class knownType) { 24 | 25 | json.writeArrayStart(name); 26 | 27 | for (int i = 0; i < object.size; i++) { 28 | json.writeValue(object.get(i), array.value()); 29 | } 30 | 31 | json.writeArrayEnd(); 32 | } 33 | 34 | @Override 35 | @SuppressWarnings("unchecked") 36 | public Array read(Json json, JsonValue jsonData, Class type) { 37 | 38 | JsonValue entry = jsonData.getChild(name); 39 | JsonValue entry2 = entry; 40 | 41 | // pre-scan size of array 42 | 43 | int size = 0; 44 | while (entry2 != null) { 45 | size++; 46 | entry2 = entry2.next; 47 | } 48 | 49 | // create array, read data 50 | 51 | Array values; 52 | 53 | try { 54 | 55 | V[] items = (V[]) ArrayReflection.newInstance(array.value(), size); 56 | 57 | values = (Array) array.array().newInstance(); 58 | values.items = items; 59 | values.ordered = array.ordered(); 60 | 61 | } catch (ClassCastException | InstantiationException | IllegalAccessException e) { 62 | throw new GdxRuntimeException(e); 63 | } 64 | 65 | while (entry != null) { 66 | 67 | V value = json.readValue((Class) array.value(), entry); 68 | 69 | values.add(value); 70 | 71 | entry = entry.next; 72 | } 73 | 74 | return values; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/json/JsonFloatSerializer.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.json; 2 | 3 | import com.badlogic.gdx.concurrent.ThreadLocalInstance; 4 | import com.badlogic.gdx.utils.GdxRuntimeException; 5 | import com.badlogic.gdx.utils.StringBuilder; 6 | 7 | import java.util.regex.Matcher; 8 | import java.util.regex.Pattern; 9 | 10 | /** 11 | * Utility functions to (optionally) store floats and doubles using a IEEE-754 bit masks. 12 | *

13 | * Reference: http://the-witness.net/news/2011/12/engine-tech-concurrent-world-editing/ 14 | */ 15 | public class JsonFloatSerializer { 16 | 17 | private static final String fpRegEx = "(-?[0-9.]+(E[+-][0-9]+)?)"; 18 | private static final Pattern purePattern = Pattern.compile("^" + fpRegEx + "$"); 19 | private static final Pattern ieeePattern = Pattern.compile("^0x([a-fA-F0-9]+)\\|" + fpRegEx + "$"); 20 | 21 | private static final ThreadLocal stringBuilder = 22 | new ThreadLocalInstance<>(() -> new StringBuilder(32)); 23 | 24 | public static String encodeFloatBits(float value) { 25 | 26 | StringBuilder builder = stringBuilder.get(); 27 | 28 | builder.setLength(0); 29 | builder.append("0x"); 30 | builder.append(String.format("%08X", Float.floatToRawIntBits(value))); 31 | builder.append("|"); 32 | builder.append(value); 33 | 34 | return builder.toString(); 35 | } 36 | 37 | public static float decodeFloatBits(String value, float defaultValue) { 38 | 39 | if (value == null) { 40 | return defaultValue; 41 | } 42 | 43 | Matcher matcher = ieeePattern.matcher(value); 44 | 45 | if (matcher.matches()) { 46 | String group = matcher.group(1); 47 | return Float.intBitsToFloat((int) Long.parseLong(group, 16)); 48 | } else { 49 | matcher = purePattern.matcher(value); 50 | if (matcher.matches()) { 51 | String group = matcher.group(1); 52 | return Float.parseFloat(group); 53 | } 54 | } 55 | 56 | throw new GdxRuntimeException("Error parsing float from string: " + value); 57 | } 58 | 59 | public static String encodeDoubleBits(double value) { 60 | 61 | StringBuilder builder = stringBuilder.get(); 62 | 63 | builder.setLength(0); 64 | builder.append("0x"); 65 | builder.append(String.format("%016X", Double.doubleToRawLongBits(value))); 66 | builder.append("|"); 67 | builder.append(value); 68 | 69 | return builder.toString(); 70 | } 71 | 72 | public static double decodeDoubleBits(String value, double defaultValue) { 73 | 74 | if (value == null) { 75 | return defaultValue; 76 | } 77 | 78 | Matcher matcher = ieeePattern.matcher(value); 79 | 80 | if (matcher.matches()) { 81 | String group = matcher.group(1); 82 | return Double.longBitsToDouble(Long.parseLong(group, 16)); 83 | } else { 84 | matcher = purePattern.matcher(value); 85 | if (matcher.matches()) { 86 | String group = matcher.group(1); 87 | return Double.parseDouble(group); 88 | } 89 | } 90 | 91 | throw new GdxRuntimeException("Error parsing double from string: " + value); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/json/JsonMapSerializer.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.json; 2 | 3 | import com.badlogic.gdx.function.Function; 4 | import com.badlogic.gdx.json.annotations.JsonMap; 5 | import com.badlogic.gdx.utils.*; 6 | 7 | import java.util.Iterator; 8 | import java.util.Map; 9 | import com.badlogic.gdx.function.Supplier; 10 | 11 | /** 12 | * Implementation of {@link com.badlogic.gdx.utils.Json.Serializer} to serialize {@link Map} containers. 13 | *

14 | * This is used internally by {@link AnnotatedJsonSerializer}. 15 | */ 16 | class JsonMapSerializer implements Json.Serializer> { 17 | 18 | private String name; 19 | private JsonMap map; 20 | 21 | JsonMapSerializer(String name, JsonMap map) { 22 | this.name = name; 23 | this.map = map; 24 | } 25 | 26 | @Override 27 | public void write(Json json, Iterable object, Class knownType) { 28 | throw new GdxRuntimeException("Not implemented!"); 29 | } 30 | 31 | public void write(Json json, Iterable entries, 32 | Function getKey, Function getValue) { 33 | 34 | Iterator it = entries.iterator(); 35 | 36 | json.writeArrayStart(name); 37 | 38 | while (it.hasNext()) { 39 | 40 | E entry = it.next(); 41 | 42 | json.writeObjectStart(); 43 | 44 | json.writeValue("key", getKey.apply(entry), map.key()); 45 | json.writeValue("value", getValue.apply(entry), map.value()); 46 | 47 | json.writeObjectEnd(); 48 | } 49 | 50 | json.writeArrayEnd(); 51 | } 52 | 53 | @Override 54 | public Iterable read(Json json, JsonValue jsonData, Class type) { 55 | throw new GdxRuntimeException("Not implemented!"); 56 | } 57 | 58 | @SuppressWarnings("unchecked") 59 | public Map read(Json json, JsonValue jsonData) { 60 | 61 | Map values; 62 | 63 | try { 64 | values = (Map) map.map().newInstance(); 65 | } catch (InstantiationException | IllegalAccessException e) { 66 | throw new GdxRuntimeException(e); 67 | } 68 | 69 | JsonValue entry = jsonData.getChild(name); 70 | 71 | while (entry != null) { 72 | 73 | JsonValue keyValue = entry.get("key"); 74 | K key = json.readValue((Class) map.key(), keyValue); 75 | 76 | JsonValue valueValue = entry.get("value"); 77 | V value = json.readValue((Class) map.value(), valueValue); 78 | 79 | values.put(key, value); 80 | 81 | entry = entry.next; 82 | } 83 | 84 | return values; 85 | } 86 | 87 | interface KeyValueConsumer { 88 | 89 | void accept(M map, K key, V value); 90 | } 91 | 92 | @SuppressWarnings("unchecked") 93 | public M read(Json json, JsonValue jsonData, 94 | Supplier newInstance, KeyValueConsumer put) { 95 | 96 | M values = newInstance.get(); 97 | 98 | JsonValue entry = jsonData.getChild(name); 99 | 100 | while (entry != null) { 101 | 102 | JsonValue keyValue = entry.get("key"); 103 | K key = json.readValue((Class) map.key(), keyValue); 104 | 105 | JsonValue valueValue = entry.get("value"); 106 | V value = json.readValue((Class) map.value(), valueValue); 107 | 108 | put.accept(values, key, value); 109 | 110 | entry = entry.next; 111 | } 112 | 113 | return values; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/json/annotations/JsonArray.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.json.annotations; 2 | 3 | import com.badlogic.gdx.utils.Array; 4 | 5 | import java.lang.annotation.*; 6 | 7 | @Target(ElementType.FIELD) 8 | @Retention(RetentionPolicy.RUNTIME) 9 | public @interface JsonArray { 10 | 11 | /** 12 | * Used to construct the correct container class which must be derived from {@link Array}. 13 | */ 14 | Class array() default Array.class; 15 | 16 | /** 17 | * Specifies class type of array elements. 18 | */ 19 | Class value(); 20 | 21 | /** 22 | * Used as parameter during {@link com.badlogic.gdx.utils.Array#Array(boolean, int)} 23 | * construction on deserialization. 24 | */ 25 | boolean ordered() default true; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/json/annotations/JsonMap.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.json.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | @Target(ElementType.FIELD) 6 | @Retention(RetentionPolicy.RUNTIME) 7 | public @interface JsonMap { 8 | Class map(); 9 | Class key(); 10 | Class value(); 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/json/annotations/JsonSerializable.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.json.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * Class annotation used to prepare classes for use with 7 | * {@link com.badlogic.gdx.json.AnnotatedJsonSerializer}. 8 | */ 9 | @Inherited 10 | @Target(ElementType.TYPE) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface JsonSerializable { 13 | 14 | /** 15 | * If dynamic == true, {@link com.badlogic.gdx.json.AnnotatedJsonSerializer} writes additional 16 | * type information for each instance of the annotated class. This type information is then used 17 | * during deserialization to reconstruct the correct (derived) instance type. 18 | */ 19 | boolean dynamic() default false; 20 | 21 | /** 22 | * If fullyQualifiedClassTag == true, this class does not register a tag, which means that the fully 23 | * qualified class name is written if dynamic == true. 24 | */ 25 | boolean fullyQualifiedClassTag() default false; 26 | 27 | /** 28 | * If encodeFP == true, floats and doubles are written to (and parsed from) JSON as special format strings. 29 | * @see com.badlogic.gdx.json.JsonFloatSerializer 30 | */ 31 | boolean encodeFP() default false; 32 | 33 | /** 34 | * By default, null values are not written to JSON. 35 | */ 36 | boolean writeNull() default false; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/json/annotations/JsonSerialize.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.json.annotations; 2 | 3 | import java.lang.annotation.*; 4 | 5 | /** 6 | * Field annotation to mark members of a {@link JsonSerializable} class as subject to serialization. 7 | *

8 | * If the optional "name" property is not set, the identifier of the field is used to name the JSON object. 9 | *

10 | * The {@link JsonArray} property must be set to add type information to serializable fields of type 11 | * {@link com.badlogic.gdx.utils.Array}. 12 | *

13 | * The {@link JsonMap} property must be set to add type information for {@link java.util.Map} containers. 14 | */ 15 | @Target(ElementType.FIELD) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | public @interface JsonSerialize { 18 | 19 | String name() default ""; 20 | 21 | JsonArray[] array() default {}; 22 | 23 | JsonMap[] map() default {}; 24 | 25 | /** 26 | * If the JSON value is 'null', or not present at all, an instance is still 27 | * created by default if the annotated field is serializable. 28 | *

29 | * Set this to property to false to prevent that. 30 | */ 31 | boolean createIfNull() default true; 32 | 33 | boolean writeIfDefaultValue() default true; 34 | 35 | int defaultIntValue() default 0; 36 | 37 | float defaultFloatValue() default 0.0f; 38 | 39 | double defaultDoubleValue() default 0.0; 40 | 41 | boolean defaultBooleanValue() default false; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/lang/Box.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.lang; 2 | 3 | import com.badlogic.gdx.concurrent.ThreadLocalInstance; 4 | import com.badlogic.gdx.function.Consumer; 5 | 6 | import java.lang.reflect.Array; 7 | 8 | /** 9 | * Utility class to box typed values. Can be used to capture non-final 10 | * variables for lambda functions. 11 | * 12 | *

 13 |  * {@code
 14 |  * int getSum(Iterable container) {
 15 |  *     final Box.Integer sum = new Box.Integer(0);
 16 |  *     container.forEach(element -> sum.set(sum.get() + element));
 17 |  *     return sum.get();
 18 |  * }
 19 |  * }
 20 |  * 
21 | * 22 | * For boxed primitive types there is a small, thread-local storage which holds 23 | * one per-thread instance of each sub-class. 24 | * 25 | *
 26 |  * {@code
 27 |  * int getSum(Iterable container) {
 28 |  *     final Box.Integer sum = Box.borrowInteger();
 29 |  *     sum.set(0);
 30 |  *     container.forEach(element -> sum.set(sum.get() + element));
 31 |  *     int result = sum.get();
 32 |  *     Box.releaseInteger();
 33 |  *     return result;
 34 |  * }
 35 |  * }
 36 |  * 
37 | * 38 | * There's also a version which uses try-with-resources internally. 39 | * 40 | *
 41 |  * {@code
 42 |  * int getSum(Iterable container) {
 43 |  * 	   return Box.withInteger(sum -> {
 44 |  *         sum.set(0);
 45 |  *         container.forEach(element -> sum.set(sum.get() + element));
 46 |  * 	   });
 47 |  * }
 48 |  * }
 49 |  * 
50 | * 51 | */ 52 | public final class Box { 53 | 54 | public static final class Boolean { 55 | 56 | private boolean value; 57 | 58 | @SuppressWarnings("unused") 59 | Boolean() { 60 | this.value = false; 61 | } 62 | 63 | public Boolean(boolean value) { 64 | this.value = value; 65 | } 66 | 67 | public boolean get() { 68 | return value; 69 | } 70 | 71 | public Boolean set(boolean value) { 72 | this.value = value; 73 | return this; 74 | } 75 | 76 | } 77 | 78 | public static final class Integer { 79 | 80 | private int value; 81 | 82 | @SuppressWarnings("unused") 83 | Integer() { 84 | this.value = 0; 85 | } 86 | 87 | public Integer(int value) { 88 | this.value = value; 89 | } 90 | 91 | public int get() { 92 | return value; 93 | } 94 | 95 | public int getAndIncrement() { 96 | return value++; 97 | } 98 | 99 | public Integer set(int value) { 100 | this.value = value; 101 | return this; 102 | } 103 | 104 | public Integer add(int value) { 105 | this.value += value; 106 | return this; 107 | } 108 | 109 | } 110 | 111 | public static final class Float { 112 | 113 | private float value; 114 | 115 | @SuppressWarnings("unused") 116 | Float() { 117 | this.value = 0.0f; 118 | } 119 | 120 | public Float(float value) { 121 | this.value = value; 122 | } 123 | 124 | public float get() { 125 | return value; 126 | } 127 | 128 | public Float set(float value) { 129 | this.value = value; 130 | return this; 131 | } 132 | 133 | } 134 | 135 | public static final class Reference { 136 | 137 | private R value; 138 | 139 | public Reference(R value) { 140 | this.value = value; 141 | } 142 | 143 | public R get() { 144 | return value; 145 | } 146 | 147 | public boolean isNull() { 148 | return value == null; 149 | } 150 | 151 | public Reference set(R value) { 152 | this.value = value; 153 | return this; 154 | } 155 | 156 | } 157 | 158 | private static class BorrowChecker implements AutoCloseable { 159 | 160 | private final B[] references; 161 | private int locks = 0; 162 | 163 | private static final int cacheSize = 4; 164 | 165 | @SuppressWarnings("unchecked") 166 | private BorrowChecker(Class clazz) { 167 | try { 168 | references = (B[]) Array.newInstance(clazz, cacheSize); 169 | for (int i = 0; i < cacheSize; i++) { 170 | references[i] = clazz.newInstance(); 171 | } 172 | } catch (InstantiationException | IllegalAccessException e) { 173 | throw new RuntimeException(e); 174 | } 175 | } 176 | 177 | BorrowChecker borrow() { 178 | if (locks >= cacheSize) { 179 | throw new RuntimeException("Too many nested borrows!"); 180 | } 181 | locks++; 182 | return this; 183 | } 184 | 185 | B reference() { 186 | return references[locks - 1]; 187 | } 188 | 189 | @Override 190 | public void close() { 191 | locks--; 192 | } 193 | 194 | } 195 | 196 | private static final ThreadLocal> tlsBoolean = 197 | new ThreadLocalInstance<>(() -> new BorrowChecker<>(Boolean.class)); 198 | 199 | private static final ThreadLocal> tlsInteger = 200 | new ThreadLocalInstance<>(() -> new BorrowChecker<>(Integer.class)); 201 | 202 | private static final ThreadLocal> tlsFloat = 203 | new ThreadLocalInstance<>(() -> new BorrowChecker<>(Float.class)); 204 | 205 | public static Boolean borrowBoolean() { 206 | return tlsBoolean.get().borrow().reference(); 207 | } 208 | 209 | public static boolean releaseBoolean() { 210 | BorrowChecker value = tlsBoolean.get(); 211 | boolean result = value.reference().get(); 212 | value.close(); 213 | return result; 214 | } 215 | 216 | public static boolean withBoolean(Consumer consumer) { 217 | try (BorrowChecker value = tlsBoolean.get().borrow()) { 218 | consumer.accept(value.reference()); 219 | return value.reference().get(); 220 | } 221 | } 222 | 223 | public static Integer borrowInteger() { 224 | return tlsInteger.get().borrow().reference(); 225 | } 226 | 227 | public static int releaseInteger() { 228 | BorrowChecker value = tlsInteger.get(); 229 | int result = value.reference().get(); 230 | value.close(); 231 | return result; 232 | } 233 | 234 | public static int withInteger(Consumer consumer) { 235 | try (BorrowChecker value = tlsInteger.get().borrow()) { 236 | consumer.accept(value.reference()); 237 | return value.reference().get(); 238 | } 239 | } 240 | 241 | public static float withFloat(Consumer consumer) { 242 | try (BorrowChecker value = tlsFloat.get().borrow()) { 243 | consumer.accept(value.reference()); 244 | return value.reference().get(); 245 | } 246 | } 247 | 248 | } 249 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/lang/ClassFinder.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.lang; 2 | 3 | import com.badlogic.gdx.files.FileHandle; 4 | import com.badlogic.gdx.function.Consumer; 5 | import com.badlogic.gdx.function.Predicate; 6 | import com.badlogic.gdx.utils.Array; 7 | import com.badlogic.gdx.utils.GdxRuntimeException; 8 | 9 | import java.io.File; 10 | import java.io.IOException; 11 | import java.net.URISyntaxException; 12 | import java.net.URL; 13 | import java.nio.file.Path; 14 | import java.nio.file.Paths; 15 | import java.util.Enumeration; 16 | import java.util.jar.JarEntry; 17 | import java.util.jar.JarFile; 18 | 19 | /** 20 | * Utility class to filter and iterate classes registered to a {@link ClassLoader}. 21 | */ 22 | public class ClassFinder { 23 | 24 | private Array urls = new Array<>(); 25 | 26 | /** 27 | * Only uses the same URL as the given class was loaded from. 28 | *

29 | * Must be called before {@link ClassFinder#process(Predicate, Consumer)}. 30 | */ 31 | public ClassFinder filterURLforClass(Class clazz) { 32 | 33 | // todo: this may not work because of SecurityManager 34 | URL url = clazz.getProtectionDomain().getCodeSource().getLocation(); 35 | urls.add(url); 36 | 37 | return this; 38 | } 39 | 40 | /** 41 | * Iterates classes of all URLs filtered by a previous call of {@link ClassFinder#filterURLforClass(Class)}. 42 | */ 43 | public ClassFinder process(Predicate filter, Consumer> processor) { 44 | 45 | for (URL url : urls) { 46 | 47 | Path path; 48 | 49 | // required for Windows paths with spaces 50 | try { 51 | path = Paths.get(url.toURI()); 52 | } catch (URISyntaxException e) { 53 | throw new GdxRuntimeException(e); 54 | } 55 | 56 | FileHandle file = new FileHandle(path.toFile()); 57 | 58 | if (file.isDirectory()) { 59 | 60 | processDirectory(file, file, filter, processor); 61 | 62 | } else if (file.extension().equals("jar")) { 63 | 64 | try (JarFile jar = new JarFile(file.file())) { 65 | 66 | Enumeration entries = jar.entries(); 67 | 68 | while (entries.hasMoreElements()) { 69 | 70 | JarEntry entry = entries.nextElement(); 71 | 72 | if (entry.isDirectory()) { 73 | continue; 74 | } 75 | 76 | FileHandle entryFile = new FileHandle(entry.getName()); 77 | 78 | process(null, entryFile, filter, processor); 79 | } 80 | 81 | } catch (IOException ignored) { 82 | 83 | } 84 | } 85 | 86 | } 87 | 88 | return this; 89 | } 90 | 91 | /** 92 | * Iterates classes from a {@link ClassFinderCache}. 93 | *

94 | * This is faster than walking URLs/directories. It's also more secure/portable. 95 | */ 96 | public ClassFinder process(ClassFinderCache cache, String groupName, 97 | Predicate filter, Consumer> processor) { 98 | 99 | Array classNames = cache.get(groupName); 100 | 101 | if (classNames == null) { 102 | return this; 103 | } 104 | 105 | for (String className : classNames) { 106 | processClass(className, filter, processor); 107 | } 108 | 109 | return this; 110 | } 111 | 112 | private void processDirectory(FileHandle root, FileHandle directory, 113 | Predicate filter, Consumer> processor) { 114 | 115 | FileHandle[] files = directory.list(); 116 | 117 | for (FileHandle file : files) { 118 | 119 | if (file.isDirectory()) { 120 | processDirectory(root, new FileHandle(new File(directory.file(), file.name())), filter, processor); 121 | } else { 122 | process(root, file, filter, processor); 123 | } 124 | 125 | } 126 | } 127 | 128 | private void process(FileHandle root, FileHandle file, Predicate filter, Consumer> processor) { 129 | 130 | if (!file.extension().equals("class")) { 131 | return; 132 | } 133 | 134 | FileHandle relative = relativeTo(file, root); 135 | 136 | String className = relative.pathWithoutExtension() 137 | .replace("/", ".");//.replaceAll("\\$.", ""); 138 | 139 | processClass(className, filter, processor); 140 | } 141 | 142 | private void processClass(String className, Predicate filter, Consumer> processor) { 143 | 144 | if (!filter.test(className)) { 145 | return; 146 | } 147 | 148 | try { 149 | 150 | Class clazz = Class.forName(className); 151 | processClass(clazz, filter, processor); 152 | 153 | } catch (ClassNotFoundException ignored) { 154 | 155 | } 156 | 157 | } 158 | 159 | private void processClass(Class clazz, Predicate filter, Consumer> processor) { 160 | 161 | if (!filter.test(clazz.getName())) { 162 | return; 163 | } 164 | 165 | processor.accept(clazz); 166 | 167 | for (Class inner : clazz.getDeclaredClasses()) { 168 | processClass(inner, filter, processor); 169 | } 170 | 171 | } 172 | 173 | private FileHandle relativeTo(FileHandle file, FileHandle root) { 174 | 175 | if (root == null) { 176 | return file; 177 | } 178 | 179 | String r = root.path(); 180 | String f = file.path(); 181 | 182 | if (!f.startsWith(r)) { 183 | return file; 184 | } 185 | 186 | return new FileHandle(f.substring(r.length() + 1)); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/lang/ClassFinderCache.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.lang; 2 | 3 | import com.badlogic.gdx.files.FileHandle; 4 | import com.badlogic.gdx.files.TextFileUtils; 5 | import com.badlogic.gdx.function.Supplier; 6 | import com.badlogic.gdx.utils.Array; 7 | import com.badlogic.gdx.utils.ObjectMap; 8 | 9 | import java.io.IOException; 10 | import java.io.Writer; 11 | 12 | public class ClassFinderCache { 13 | 14 | private final ObjectMap> classNames = new ObjectMap<>(); 15 | 16 | public ClassFinderCache() { 17 | 18 | } 19 | 20 | public ClassFinderCache(FileHandle file) throws IOException { 21 | readFromFile(file); 22 | } 23 | 24 | public Array get(String groupName) { 25 | return classNames.get(groupName); 26 | } 27 | 28 | public void add(String groupName, Supplier> supplier) { 29 | classNames.put(groupName, new Array<>()); 30 | classNames.get(groupName).addAll(supplier.get()); 31 | } 32 | 33 | public void writeToFile(FileHandle file) throws IOException { 34 | 35 | try (Writer writer = file.writer(false, "UTF-8")) { 36 | 37 | for (ObjectMap.Entry> group : classNames) { 38 | 39 | writer.append(':').append(group.key).append('\n'); 40 | 41 | for (String name : group.value) { 42 | writer.append(name).append('\n'); 43 | } 44 | } 45 | 46 | writer.flush(); 47 | } 48 | } 49 | 50 | private void readFromFile(FileHandle file) throws IOException { 51 | 52 | if (!file.exists()) { 53 | return; 54 | } 55 | 56 | Box.Reference groupName = new Box.Reference<>(null); 57 | 58 | TextFileUtils.readLines(file, line -> { 59 | if (line.startsWith(":")) { 60 | groupName.set(line.substring(1)); 61 | classNames.put(groupName.get(), new Array<>()); 62 | } else if (!groupName.isNull()) { 63 | classNames.get(groupName.get()).add(line); 64 | } 65 | }); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/lang/ClassUtils.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.lang; 2 | 3 | import com.badlogic.gdx.checksum.SHA1; 4 | import com.badlogic.gdx.files.FileStreamReader; 5 | import com.badlogic.gdx.utils.GdxRuntimeException; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.lang.reflect.ParameterizedType; 10 | import java.lang.reflect.Type; 11 | 12 | public class ClassUtils { 13 | 14 | /** 15 | * Uses reflection to obtain the class type of a generic type parameter. 16 | */ 17 | public static Class getClassOfGenericType(Class genericType, int typeArgument) { 18 | 19 | Type generic = genericType.getGenericSuperclass(); 20 | Type type = ((ParameterizedType) generic).getActualTypeArguments()[typeArgument]; 21 | 22 | String[] types = type.toString().split(" "); 23 | if (types.length < 2) { 24 | return null; 25 | } 26 | 27 | String className = types[1]; 28 | 29 | try { 30 | return Class.forName(className); 31 | } catch (ClassNotFoundException e) { 32 | throw new GdxRuntimeException(e); 33 | } 34 | 35 | } 36 | 37 | /** 38 | * Calculates the SHA-1 hash on the byte code (the .class file content) of the given {@link Class}. 39 | */ 40 | public static SHA1 getClassHash(Class classType) throws IOException { 41 | 42 | String resourcePath = "/" + classType.getName().replace('.', '/') + ".class"; 43 | InputStream resource = classType.getResourceAsStream(resourcePath); 44 | 45 | return FileStreamReader.hashStream(resource); 46 | } 47 | 48 | /** 49 | * Calculates the SHA-1 hash on the byte code (the .class file content) of the given {@link Class}. 50 | *

51 | * This version returns {@code defaultHash} if an {@link IOException} is thrown. 52 | */ 53 | public static SHA1 getClassHash(Class classType, SHA1 defaultHash) { 54 | try { 55 | return getClassHash(classType); 56 | } catch (IOException e) { 57 | return defaultHash; 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/lang/FourCC.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.lang; 2 | 3 | public final class FourCC { 4 | 5 | private int value; 6 | 7 | public FourCC(CharSequence name) { 8 | this(convert(name)); 9 | } 10 | 11 | public FourCC(char c0, char c1, char c2, char c3) { 12 | this(convert(c0, c1, c2, c3)); 13 | } 14 | 15 | public FourCC(int value) { 16 | this.value = value; 17 | } 18 | 19 | @Override 20 | public int hashCode() { 21 | return value; 22 | } 23 | 24 | @Override 25 | public boolean equals(Object obj) { 26 | if (obj instanceof FourCC) { 27 | return value == ((FourCC) obj).value; 28 | } 29 | return false; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return Character.toString((char) ((value >>> 24) & 0xff)) 35 | + Character.toString((char) ((value >>> 16) & 0xff)) 36 | + Character.toString((char) ((value >>> 8) & 0xff)) 37 | + Character.toString((char) (value & 0xff)); 38 | } 39 | 40 | private static int convert(CharSequence name) { 41 | return convert(name.charAt(0), name.charAt(1), name.charAt(2), name.charAt(3)); 42 | } 43 | 44 | private static int convert(char c0, char c1, char c2, char c3) { 45 | return (c0 << 24) | (c1 << 16) | (c2 << 8) | c3; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/profiler/Profiler.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.profiler; 2 | 3 | import com.badlogic.gdx.function.Consumer; 4 | 5 | /** 6 | * Simple functional interface to scope (wrap begin/end) sample calls. 7 | */ 8 | public interface Profiler { 9 | 10 | Sample sampleCPU(String name, boolean aggregate); 11 | 12 | default void sampleCPU(String name, boolean aggregate, T context, Consumer consumer) { 13 | consumer.accept(context); 14 | } 15 | 16 | Sample sampleOpenGL(String name); 17 | 18 | default void sampleOpenGL(String name, T context, Consumer consumer) { 19 | consumer.accept(context); 20 | } 21 | 22 | default void setThreadName(CharSequence name) { 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/profiler/RemoteryLwjgl.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.profiler; 2 | 3 | import com.badlogic.gdx.function.Consumer; 4 | import org.lwjgl.PointerBuffer; 5 | import org.lwjgl.system.MemoryStack; 6 | import org.lwjgl.util.remotery.Remotery; 7 | import org.lwjgl.util.remotery.RemoteryGL; 8 | 9 | public class RemoteryLwjgl implements Profiler { 10 | 11 | private static class SampleCPU extends Sample { 12 | 13 | @Override 14 | public void end() { 15 | Remotery.rmt_EndCPUSample(); 16 | } 17 | } 18 | 19 | private static class SampleOGL extends Sample { 20 | 21 | @Override 22 | public void end() { 23 | RemoteryGL.rmt_EndOpenGLSample(); 24 | } 25 | } 26 | 27 | private static long globalInstance = 0L; 28 | private static final Sample defaultSampleCPU = new SampleCPU(); 29 | private static final Sample defaultSampleOGL = new SampleOGL(); 30 | 31 | public static void initialize() { 32 | 33 | try (MemoryStack stack = MemoryStack.stackPush()) { 34 | 35 | PointerBuffer pb = stack.callocPointer(1); 36 | Remotery.rmt_CreateGlobalInstance(pb); 37 | 38 | globalInstance = pb.get(0); 39 | 40 | RemoteryGL.rmt_BindOpenGL(); 41 | 42 | Remotery.rmt_SetCurrentThreadName("Render"); 43 | } 44 | } 45 | 46 | public static void shutdown() { 47 | 48 | if (globalInstance != 0L) { 49 | 50 | RemoteryGL.rmt_UnbindOpenGL(); 51 | 52 | Remotery.rmt_DestroyGlobalInstance(globalInstance); 53 | globalInstance = 0L; 54 | } 55 | } 56 | 57 | @Override 58 | public Sample sampleCPU(String name, boolean aggregate) { 59 | int flags = aggregate ? Remotery.RMTSF_Aggregate : Remotery.RMTSF_None; 60 | Remotery.rmt_BeginCPUSample(name, flags, null); 61 | return defaultSampleCPU; 62 | } 63 | 64 | @Override 65 | public void sampleCPU(String name, boolean aggregate, T context, Consumer consumer) { 66 | 67 | int flags = aggregate ? Remotery.RMTSF_Aggregate : Remotery.RMTSF_None; 68 | Remotery.rmt_BeginCPUSample(name, flags, null); 69 | 70 | consumer.accept(context); 71 | 72 | Remotery.rmt_EndCPUSample(); 73 | } 74 | 75 | @Override 76 | public Sample sampleOpenGL(String name) { 77 | RemoteryGL.rmt_BeginOpenGLSample(name, null); 78 | return defaultSampleOGL; 79 | } 80 | 81 | @Override 82 | public void sampleOpenGL(String name, T context, Consumer consumer) { 83 | 84 | RemoteryGL.rmt_BeginOpenGLSample(name, null); 85 | 86 | consumer.accept(context); 87 | 88 | RemoteryGL.rmt_EndOpenGLSample(); 89 | } 90 | 91 | @Override 92 | public void setThreadName(CharSequence name) { 93 | Remotery.rmt_SetCurrentThreadName(name); 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/profiler/Sample.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.profiler; 2 | 3 | public abstract class Sample implements AutoCloseable { 4 | 5 | @Override 6 | public void close() { 7 | end(); 8 | } 9 | 10 | public abstract void end(); 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/random/RandomNumberGenerator.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.random; 2 | 3 | public interface RandomNumberGenerator { 4 | 5 | long next(); 6 | 7 | void seed(long s0, long s1); 8 | 9 | void getSeed(long[] seed); 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/random/RandomNumbers.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.random; 2 | 3 | import com.badlogic.gdx.math.RandomXS128; 4 | import com.badlogic.gdx.utils.Array; 5 | 6 | public final class RandomNumbers { 7 | 8 | private final RandomNumberGenerator generator; 9 | 10 | public RandomNumbers(RandomXS128 generator) { 11 | this(new RandomNumberGenerator() { 12 | @Override 13 | public long next() { 14 | return generator.nextLong(); 15 | } 16 | 17 | @Override 18 | public void seed(long s0, long s1) { 19 | generator.setState(s0, s1); 20 | } 21 | 22 | @Override 23 | public void getSeed(long[] seed) { 24 | seed[0] = generator.getState(0); 25 | seed[1] = generator.getState(1); 26 | } 27 | }); 28 | } 29 | 30 | public RandomNumbers(RandomNumberGenerator generator) { 31 | this.generator = generator; 32 | } 33 | 34 | public int nextInt() { 35 | return (int) generator.next(); 36 | } 37 | 38 | public int nextInt(final int n) { 39 | return (int) nextLongExclusive(n + 1); 40 | } 41 | 42 | /** 43 | * @return random number between start and end (including end value) 44 | */ 45 | public int nextInt(int start, int end) { 46 | return start + nextInt(end - start); 47 | } 48 | 49 | public long nextLong() { 50 | return generator.next(); 51 | } 52 | 53 | public long nextLong(long n) { 54 | return nextLongExclusive(n + 1); 55 | } 56 | 57 | private long nextLongExclusive(final long n) { 58 | 59 | if (n <= 0) { 60 | throw new IllegalArgumentException("n must be positive"); 61 | } 62 | 63 | long t = generator.next(); 64 | final long nMinus1 = n - 1; 65 | 66 | if ((n & nMinus1) == 0) { 67 | return t & nMinus1; 68 | } 69 | 70 | for (long u = t >>> 1; u + nMinus1 - (t = u % n) < 0; u = generator.next() >>> 1) { 71 | /* empty */ 72 | } 73 | 74 | return t; 75 | } 76 | 77 | public double nextDouble() { 78 | return Double.longBitsToDouble(generator.next() >>> 12 | 0x3FFL << 52) - 1.0; 79 | } 80 | 81 | public float nextFloat() { 82 | return Float.intBitsToFloat((int) (generator.next() >>> 41) | 0x3F8 << 20) - 1.0f; 83 | } 84 | 85 | public boolean nextBoolean() { 86 | return (generator.next() & 1) != 0; 87 | } 88 | 89 | /** 90 | * Pick a random array member. Returns null if the array is empty. 91 | */ 92 | public T nextInArray(Array array) { 93 | 94 | if (array.size == 0) { 95 | return null; 96 | } 97 | 98 | return array.get(nextInt(array.size - 1)); 99 | } 100 | 101 | /** 102 | * Uses reflection to return a random enum value. Has a relatively large runtime overhead. 103 | */ 104 | public > T nextEnum(Class enumType) { 105 | T[] values = enumType.getEnumConstants(); 106 | return values[nextInt(values.length - 1)]; 107 | } 108 | 109 | public void seed(long s0, long s1) { 110 | generator.seed(s0, s1); 111 | } 112 | 113 | public void getSeed(long[] seed) { 114 | generator.getSeed(seed); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/random/XoRoShiRo128Plus.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.random; 2 | 3 | /** 4 | * Slim Java implementation of the xoroshiro128plus pseudo-random number generator 5 | * written in 2016 by David Blackman and Sebastiano Vigna. 6 | * 7 | * http://xoroshiro.di.unimi.it/ 8 | * http://dsiutils.di.unimi.it/ 9 | */ 10 | public final class XoRoShiRo128Plus implements RandomNumberGenerator { 11 | 12 | private long s0, s1; 13 | private final static long[] JUMP = { 0xbeac0467eba5facbL, 0xd86b048b86aa9922L }; 14 | 15 | /** 16 | * "The state must be seeded so that it is not everywhere zero." 17 | */ 18 | public XoRoShiRo128Plus(long s0, long s1) { 19 | seed(s0, s1); 20 | } 21 | 22 | @Override 23 | public long next() { 24 | long s0 = this.s0; 25 | long s1 = this.s1; 26 | long result = s0 + s1; 27 | 28 | s1 ^= s0; 29 | this.s0 = Long.rotateLeft(s0, 55) ^ s1 ^ (s1 << 14); 30 | this.s1 = Long.rotateLeft(s1, 36); 31 | 32 | return result; 33 | } 34 | 35 | public void jump() { 36 | long s0 = 0; 37 | long s1 = 0; 38 | 39 | for (int i = 0; i < JUMP.length; i++) { 40 | for (int b = 0; b < 64; b++) { 41 | if ((JUMP[i] & 1L << b) != 0) { 42 | s0 ^= this.s0; 43 | s1 ^= this.s1; 44 | } 45 | next(); 46 | } 47 | } 48 | 49 | this.s0 = s0; 50 | this.s1 = s1; 51 | } 52 | 53 | public void seed(long s0, long s1) { 54 | this.s0 = s0; 55 | this.s1 = s1; 56 | } 57 | 58 | @Override 59 | public void getSeed(long[] out) { 60 | out[0] = s0; 61 | out[1] = s1; 62 | } 63 | 64 | } 65 | 66 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/scenes/scene2d/ui/ActorLayout.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.scenes.scene2d.ui; 2 | 3 | 4 | import com.badlogic.gdx.checksum.CRC32; 5 | import com.badlogic.gdx.json.AnnotatedJsonObject; 6 | import com.badlogic.gdx.json.annotations.JsonSerializable; 7 | import com.badlogic.gdx.json.annotations.JsonSerialize; 8 | import com.badlogic.gdx.scenes.scene2d.Actor; 9 | import com.badlogic.gdx.scenes.scene2d.Touchable; 10 | import com.badlogic.gdx.utils.GdxRuntimeException; 11 | 12 | @JsonSerializable(dynamic = true) 13 | public abstract class ActorLayout implements AnnotatedJsonObject { 14 | 15 | @JsonSerialize 16 | public String name; 17 | 18 | @JsonSerialize 19 | public String style = "default"; 20 | 21 | @JsonSerialize 22 | public BaseLayout layout; 23 | 24 | @JsonSerialize 25 | public boolean visible = true; 26 | 27 | ActorLayout(Class actorClass) { 28 | this.actorClass = actorClass; 29 | } 30 | 31 | @Override 32 | public void onJsonWrite() { 33 | /* not implemented */ 34 | } 35 | 36 | public int nameId; 37 | Class actorClass; 38 | 39 | @Override 40 | public void onJsonRead() { 41 | 42 | if (layout == null) { 43 | layout = new BaseLayout(); 44 | } 45 | 46 | if (name == null) { 47 | throw new GdxRuntimeException("ActorLayout name is null. It needs a name field!"); 48 | } 49 | 50 | nameId = CRC32.calculateString(name).hashCode(); 51 | } 52 | 53 | protected Actor create(Skin skin, StageLayoutListener listener) { 54 | 55 | // create actor, using layout data 56 | T actor = createActor(skin, listener); 57 | 58 | // set name for later lookups 59 | actor.setName(name); 60 | 61 | actor.setTouchable(layout.touchable ? Touchable.enabled : Touchable.disabled); 62 | actor.setVisible(visible); 63 | 64 | return actor; 65 | } 66 | 67 | protected abstract T createActor(Skin skin, StageLayoutListener listener); 68 | 69 | } -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/scenes/scene2d/ui/BaseLayout.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.scenes.scene2d.ui; 2 | 3 | import com.badlogic.gdx.json.annotations.JsonSerializable; 4 | import com.badlogic.gdx.json.annotations.JsonSerialize; 5 | import com.badlogic.gdx.math.MathUtils; 6 | import com.badlogic.gdx.math.Vector2; 7 | import com.badlogic.gdx.scenes.scene2d.*; 8 | import com.badlogic.gdx.utils.viewport.Viewport; 9 | 10 | @JsonSerializable 11 | public class BaseLayout { 12 | 13 | @JsonSerialize 14 | public int x = 0; 15 | 16 | @JsonSerialize 17 | public int y = 0; 18 | 19 | @JsonSerialize 20 | public int width = 0; 21 | 22 | @JsonSerialize 23 | public int height = 0; 24 | 25 | @JsonSerialize 26 | public Align anchor = Align.Center; 27 | 28 | @JsonSerialize 29 | public boolean touchable = true; 30 | 31 | public enum Align { 32 | Center, 33 | Left, 34 | Right, 35 | Bottom, 36 | Top, 37 | BottomLeft, 38 | BottomRight, 39 | TopLeft, 40 | TopRight 41 | } 42 | 43 | void resize(Stage stage, Actor actor, boolean root) { 44 | 45 | int parentWidth, parentHeight; 46 | 47 | if (root) { 48 | Viewport viewport = stage.getViewport(); 49 | 50 | parentWidth = viewport.getScreenWidth(); 51 | parentHeight = viewport.getScreenHeight(); 52 | 53 | } else { 54 | Actor parent = actor.getParent(); 55 | 56 | parentWidth = MathUtils.floor(parent.getWidth()); 57 | parentHeight = MathUtils.floor(parent.getHeight()); 58 | } 59 | 60 | int actorWidth = MathUtils.floor(actor.getWidth()); 61 | int actorHeight = MathUtils.floor(actor.getHeight()); 62 | 63 | /** Dynamic sizing of actor if we want percentage of parent (indicated by negative width) */ 64 | if(width < 0){ 65 | actorWidth = MathUtils.clamp(-width, 1, 100) * parentWidth / 100; 66 | 67 | actor.setWidth(actorWidth); 68 | } 69 | if(height < 0){ 70 | actorHeight = MathUtils.clamp(-height, 1, 100) * parentHeight / 100; 71 | 72 | actor.setHeight(actorHeight); 73 | } 74 | 75 | int px = 0, py = 0; 76 | 77 | switch (anchor) { 78 | 79 | case Center: 80 | px = parentWidth / 2 - actorWidth / 2; 81 | py = parentHeight / 2 - actorHeight / 2; 82 | break; 83 | 84 | case Left: 85 | px = 0; 86 | py = parentHeight / 2 - actorHeight / 2; 87 | break; 88 | 89 | case Right: 90 | px = parentWidth - actorWidth; 91 | py = parentHeight / 2 - actorHeight / 2; 92 | break; 93 | 94 | case Bottom: 95 | px = parentWidth / 2 - actorWidth / 2; 96 | py = 0; 97 | break; 98 | 99 | case Top: 100 | px = parentWidth / 2 - actorWidth / 2; 101 | py = parentHeight - actorHeight; 102 | break; 103 | 104 | case BottomLeft: 105 | px = 0; 106 | py = 0; 107 | break; 108 | 109 | case BottomRight: 110 | px = parentWidth - actorWidth; 111 | py = 0; 112 | break; 113 | 114 | case TopLeft: 115 | px = 0; 116 | py = parentHeight - actorHeight; 117 | break; 118 | 119 | case TopRight: 120 | px = parentWidth - actorWidth; 121 | py = parentHeight - actorHeight; 122 | break; 123 | } 124 | 125 | px += x; 126 | py += y; 127 | 128 | actor.setPosition(px, py); 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/badlogic/gdx/scenes/scene2d/ui/ButtonLayout.java: -------------------------------------------------------------------------------- 1 | package com.badlogic.gdx.scenes.scene2d.ui; 2 | 3 | import com.badlogic.gdx.scenes.scene2d.InputEvent; 4 | import com.badlogic.gdx.scenes.scene2d.utils.ClickListener; 5 | 6 | @SuppressWarnings("unused") 7 | public class ButtonLayout extends ActorLayout