Skip to content

Commit

Permalink
Merge pull request #151 from xpenatan/master
Browse files Browse the repository at this point in the history
Release v1.1.0
  • Loading branch information
xpenatan authored Jan 30, 2025
2 parents c3edd5f + 8f37abd commit b323bee
Show file tree
Hide file tree
Showing 75 changed files with 2,405 additions and 2,809 deletions.
11 changes: 11 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
[-SNAPSHOT]

[1.1.0]
- Fix music not looping correctly
- Add disposeUnsafeByteBuffer, newUnsafeByteBuffer and getAllocatedBytesUnsafe
- Fix play music creating new music instance
- Clear mouse delta every frame
- Improve PixmapEmu and Gdx2DPixmapEmu
- Add PixmapEmu(Gdx2DPixmapEmu) constructor
- Add customizable ByteBuffer
- Update to libgdx 1.13.1

[1.0.5]
- Add Config Asset preloadListener
- Update AssetLoader and AssetDownloader
- Add AssetInstance to obtain AssetLoader or AssetDownloader
- Fix drawing to Gdx2DPixmapEmu
- Update Freetype emulation to fix build errors and script loading solution
- Update TeaVM to 0.11.0

[1.0.4]
- Fix music id
Expand Down
32 changes: 21 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
[![Sonatype Nexus (Snapshots)](https://img.shields.io/nexus/releases/com.github.xpenatan.gdx-teavm/backend-teavm?nexusVersion=2&server=https%3A%2F%2Foss.sonatype.org&label=release)](https://repo.maven.apache.org/maven2/com/github/xpenatan/gdx-teavm/)
[![Static Badge](https://img.shields.io/badge/snapshot---SNAPSHOT-red)](https://oss.sonatype.org/content/repositories/snapshots/com/github/xpenatan/gdx-teavm/)

gdx-teavm is a backend solution for running [libgdx](https://github.com/libgdx/libgdx) games in a web browser. It uses [TeaVM](https://github.com/konsoletyper/teavm) behind the scene to convert Java/Kotlin bytecode to Javascript.
**gdx-teavm** is a backend solution for running [libGDX](https://github.com/libgdx/libgdx) games directly in web browsers. It leverages [TeaVM](https://github.com/konsoletyper/teavm), a tool that compiles Java or Kotlin bytecode into JavaScript, enabling seamless execution of game logic within the browser environment without needing additional plugins or complex setup.
Additionally, gdx-teavm incorporates [Emscripten](https://emscripten.org/) to handle some of the Java Native Interface (JNI) code, allowing for the execution of specific internal functions that require native performance.

Note:
* Reflection support is very small so only reflection used in [tests](https://github.com/konsoletyper/teavm/tree/master/tests/src/test/java/org/teavm/classlib/java/lang/reflect) will work.
* TeaVM does not support every class methods from java package or JNI native methods. Check teaVM java classes [here](https://github.com/konsoletyper/teavm/blob/master/classlib/src/main/java/org/teavm/classlib).
* Kotlin [discussions](https://github.com/libktx/ktx/discussions/443).
* Box2d, Bullet, ImGui, PhysX and Freetype libraries use [emscripten](https://emscripten.org/) and [jParser](https://github.com/xpenatan/jParser) to convert C++ to Javascript/WebAssembly.


## TeaVM Examples:
* [gdx-tests](https://xpenatan.github.io/gdx-teavm/teavm/gdx-tests/) (Updated 07/09/2024)
Expand All @@ -28,6 +28,12 @@ Note:
* [Retro Commander](https://www.retrocommander.com/webapp/) ([website](https://www.retrocommander.com/))


### LibGDX Supported Versions:
| teavm-backend | LibGDX |
|---------------|--------|
| 1.0.5 | 1.12.1 |
| 1.1.0 | 1.13.1 |

## Setup:
```groovy
// Add sonatype repository to Root gradle
Expand All @@ -42,12 +48,11 @@ repositories {
isAllowInsecureProtocol = true
}
}
```
// SNAPSHOT:
gdxTeaVMVersion = "-SNAPSHOT"
// RELEASE:
gdxTeaVMVersion = "[LAST_TAG_VERSION]"
```groovy
// Version
gdxTeaVMVersion = "-SNAPSHOT"
gdxTeaVMVersion = "[LAST_TAG_VERSION]"
// In teaVM module
dependencies {
implementation "com.github.xpenatan.gdx-teavm:backend-teavm:$project.gdxTeaVMVersion"
Expand All @@ -58,9 +63,14 @@ dependencies {
```

## Supported libraries:
- [ImGui](https://github.com/xpenatan/gdx-imgui) (WIP)
- [Box2d](https://github.com/xpenatan/gdx-box2d) (WIP). Use GWTBox2d for now.
- [Bullet](https://github.com/xpenatan/gdx-bullet) (WIP)
- [PhysX](https://github.com/xpenatan/gdx-physx) (WIP)
- [ImGui](https://github.com/xpenatan/gdx-imgui) (WIP)
- [Lua](https://github.com/xpenatan/gdx-lua) (WIP)
- FreeType
- FreeType (Is included in this repo)


**ImGui**, **Box2D**, **Bullet**, **PhysX**, and **Lua** libraries leverage **[Emscripten](https://emscripten.org/)** and **[jParser](https://github.com/xpenatan/jParser)** to convert C++ code to JavaScript/WebAssembly.

**Important Note:** Most of these projects are still **works in progress** and **not yet production-ready**. If you're interested in contributing, we welcome your involvement.
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
import com.github.xpenatan.gdx.backends.teavm.TeaFileHandle;
import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.Int8ArrayWrapper;
import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.TypedArrays;
import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.Uint8ArrayWrapper;
import com.github.xpenatan.gdx.backends.teavm.gen.Emulate;
import com.github.xpenatan.gdx.backends.teavm.assetloader.AssetDownloader;
import com.github.xpenatan.gdx.backends.teavm.assetloader.AssetType;
import com.github.xpenatan.gdx.backends.teavm.assetloader.Blob;
import java.nio.ByteBuffer;
Expand All @@ -26,8 +24,9 @@ public class PixmapEmu implements Disposable {
public static PixmapEmu createFromFrameBuffer(int x, int y, int w, int h) {
Gdx.gl.glPixelStorei(GL20.GL_PACK_ALIGNMENT, 1);
final PixmapEmu pixmap = new PixmapEmu(w, h, FormatEmu.RGBA8888);
ByteBuffer pixels = pixmap.getPixels();
ByteBuffer pixels = BufferUtils.newByteBuffer(h * w * 4);
Gdx.gl.glReadPixels(x, y, w, h, GL20.GL_RGBA, GL20.GL_UNSIGNED_BYTE, pixels);
pixmap.setPixels(pixels);
return pixmap;
}

Expand Down Expand Up @@ -64,8 +63,7 @@ public static int toGlType (FormatEmu format) {
return Gdx2DPixmapEmu.toGlType(toGdx2DPixmapFormat(format));
}
}
Uint8ArrayWrapper nativePixels;
ByteBuffer buffer;

BlendingEmu blending = PixmapEmu.BlendingEmu.SourceOver;
FilterEmu filter = PixmapEmu.FilterEmu.BiLinear;

Expand Down Expand Up @@ -103,12 +101,10 @@ public PixmapEmu(FileHandle file) {
}
byte[] bytes = file.readBytes();
nativePixmap = new Gdx2DPixmapEmu(bytes, 0, bytes.length, 0);
initPixmapEmu();
}

public PixmapEmu(byte[] encodedData, int offset, int len) {
nativePixmap = new Gdx2DPixmapEmu(encodedData, offset, len, 0);
initPixmapEmu();
}

public PixmapEmu(ByteBuffer encodedData, int offset, int len) {
Expand All @@ -126,18 +122,10 @@ public PixmapEmu(int width, int height, FormatEmu format) {
nativePixmap = new Gdx2DPixmapEmu(width, height, PixmapEmu.FormatEmu.toGdx2DPixmapFormat(format));
setColor(0, 0, 0, 0);
fill();
initPixmapEmu();
}

private void initPixmapEmu() {
if(nativePixmap != null) {
nativePixels = nativePixmap.getPixels();
byte[] byteArray = TypedArrays.toByteArray(nativePixels);
buffer = ByteBuffer.wrap(byteArray);
}
else {
throw new GdxRuntimeException("NOT SUPPORTED PIXMAP");
}
public PixmapEmu (Gdx2DPixmapEmu pixmap) {
nativePixmap = pixmap;
}

public void setColor(int color) {
Expand Down Expand Up @@ -224,12 +212,13 @@ public int getGLType () {
}

public ByteBuffer getPixels() {
return buffer;
return nativePixmap.getBuffer();
}

public void setPixels(ByteBuffer pixels) {
if (!pixels.isDirect())
throw new GdxRuntimeException("Couldn't setPixels from non-direct ByteBuffer");
ByteBuffer buffer = nativePixmap.getBuffer();
BufferUtils.copy(pixels, buffer, buffer.limit());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,18 @@
import com.badlogic.gdx.graphics.GL20;
import com.badlogic.gdx.utils.Disposable;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.HasArrayBufferView;
import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.Int32ArrayWrapper;
import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.Int8ArrayNative;
import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.Int8ArrayWrapper;
import com.github.xpenatan.gdx.backends.teavm.dom.typedarray.Uint8ArrayWrapper;
import com.github.xpenatan.gdx.backends.teavm.gen.Emulate;
import java.io.InputStream;
import java.nio.ByteBuffer;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSByRef;

@Emulate(Gdx2DPixmap.class)
public class Gdx2DPixmapEmu implements Disposable {
public class Gdx2DPixmapEmu implements Disposable, Int8ArrayNative.Int8ArrayNativeListener {
public static final int GDX2D_FORMAT_ALPHA = 1;
public static final int GDX2D_FORMAT_LUMINANCE_ALPHA = 2;
public static final int GDX2D_FORMAT_RGB888 = 3;
Expand Down Expand Up @@ -61,16 +65,17 @@ public static int toGlType(int format) {
int width;
int height;
int format;
int[] nativeData = new int[4];
int heapStartIndex;
int heapEndIndex;

Uint8ArrayWrapper pixelPtr;
private Int32ArrayWrapper nativeData;
private ByteBuffer buffer = ByteBuffer.wrap(new byte[0]);;
private Int8ArrayNative nativeBuffer;

public Gdx2DPixmapEmu(byte[] encodedData, int offset, int len, int requestedFormat) {
pixelPtr = load(nativeData, encodedData, offset, len);
basePtr = nativeData[0];
width = nativeData[1];
height = nativeData[2];
format = nativeData[3];
nativeData = loadNative(encodedData, offset, len);
updateNativeData();

if(requestedFormat != 0 && requestedFormat != format) {
convert(requestedFormat);
}
Expand All @@ -80,11 +85,22 @@ public Gdx2DPixmapEmu(byte[] encodedData, int offset, int len, int requestedForm
* @throws GdxRuntimeException if allocation failed.
*/
public Gdx2DPixmapEmu(int width, int height, int format) throws GdxRuntimeException {
pixelPtr = newPixmap(nativeData, width, height, format);
this.basePtr = nativeData[0];
this.width = nativeData[1];
this.height = nativeData[2];
this.format = nativeData[3];
nativeData = newPixmapNative(width, height, format);
updateNativeData();
}

private void updateNativeData() {
this.basePtr = nativeData.get(0);
this.width = nativeData.get(1);
this.height = nativeData.get(2);
this.format = nativeData.get(3);
this.heapStartIndex = nativeData.get(4);
this.heapEndIndex = nativeData.get(5);

nativeBuffer = new Int8ArrayNative();
nativeBuffer.listener = this;
HasArrayBufferView hasArrayBufferView = (HasArrayBufferView)buffer;
hasArrayBufferView.setInt8ArrayNative(nativeBuffer);
}

// public Gdx2DPixmapEmu(ByteBuffer encodedData, int offset, int len, int requestedFormat) throws IOException {
Expand Down Expand Up @@ -144,7 +160,10 @@ private void convert(int requestedFormat) {
this.width = pixmap.width;
this.height = pixmap.height;
this.nativeData = pixmap.nativeData;
this.pixelPtr = pixmap.pixelPtr;
this.buffer = pixmap.buffer;
this.nativeBuffer = pixmap.nativeBuffer;
this.heapStartIndex = pixmap.heapStartIndex;
this.heapEndIndex = pixmap.heapEndIndex;
}

@Override
Expand Down Expand Up @@ -221,8 +240,12 @@ public static Gdx2DPixmapEmu newPixmap(int width, int height, int format) {
}
}

public Uint8ArrayWrapper getPixels() {
return pixelPtr;
public Uint8ArrayWrapper getPixels(boolean shouldCopy) {
return getHeapData(shouldCopy);
}

public ByteBuffer getBuffer() {
return buffer;
}

public int getHeight() {
Expand Down Expand Up @@ -253,6 +276,13 @@ public String getFormatString() {
return getFormatString(format);
}

public Uint8ArrayWrapper getHeapData(boolean shouldCopy) {
if(heapStartIndex == 0 && heapEndIndex == 0) {
return null;
}
return getHeapDataNative(shouldCopy, heapStartIndex, heapEndIndex);
}

static private String getFormatString(int format) {
switch(format) {
case GDX2D_FORMAT_ALPHA:
Expand All @@ -272,13 +302,23 @@ static private String getFormatString(int format) {
}
}

@JSBody(params = {"shouldCopy", "heapStartIndex", "heapEndIndex"}, script = "" +
"var heapArray = Gdx.HEAPU8.subarray(heapStartIndex, heapEndIndex);" +
"if(shouldCopy) {" +
" var newArray = new Uint8Array(heapArray);" +
" return newArray;" +
"}" +
"return heapArray;"
)
private static native Uint8ArrayWrapper getHeapDataNative(boolean shouldCopy, int heapStartIndex, int heapEndIndex);

// @off
/*JNI
#include <gdx2d/gdx2d.h>
#include <stdlib.h>
*/

@JSBody(params = {"nativeData", "buffer", "offset", "len"}, script = "" +
@JSBody(params = {"buffer", "offset", "len"}, script = "" +
"var cBufferSize = buffer.length * Uint8Array.BYTES_PER_ELEMENT;" +
"var cBuffer = Gdx._malloc(cBufferSize);" +
"Gdx.writeArrayToMemory(buffer, cBuffer);" +
Expand All @@ -289,18 +329,20 @@ static private String getFormatString(int format) {
"var format = pixmap.get_format();" +
"var width = pixmap.get_width();" +
"var height = pixmap.get_height();" +
"nativeData[0] = pixmapAddr;" +
"nativeData[1] = width;" +
"nativeData[2] = height;" +
"nativeData[3] = format;" +
"var bytesPerPixel = Gdx.Gdx.prototype.g2d_bytes_per_pixel(format);" +
"var bytesSize = width * height * bytesPerPixel;" +
"var startIndex = pixels;" +
"var endIndex = startIndex + bytesSize;" +
"var heapArray = Gdx.HEAPU8.subarray(startIndex, endIndex);" +
"return heapArray;"
"var nativeData = new Int32Array(6);" +
"nativeData[0] = pixmapAddr;" +
"nativeData[1] = width;" +
"nativeData[2] = height;" +
"nativeData[3] = format;" +
"nativeData[4] = startIndex;" +
"nativeData[5] = endIndex;" +
"return nativeData;"
)
private static native Uint8ArrayWrapper load(@JSByRef() int[] nativeData, @JSByRef() byte[] buffer, int offset, int len); /*MANUAL
private static native Int32ArrayWrapper loadNative(byte[] buffer, int offset, int len); /*MANUAL
const unsigned char* p_buffer = (const unsigned char*)env->GetPrimitiveArrayCritical(buffer, 0);
gdx2d_pixmap* pixmap = gdx2d_load(p_buffer + offset, len);
env->ReleasePrimitiveArrayCritical(buffer, (char*)p_buffer, 0);
Expand All @@ -319,25 +361,27 @@ static private String getFormatString(int format) {
return pixel_buffer;
*/

@JSBody(params = {"nativeData", "width", "height", "format"}, script = "" +
@JSBody(params = {"width", "height", "format"}, script = "" +
"var pixmap = Gdx.Gdx.prototype.g2d_new(width, height, format);" +
"var pixels = Gdx.Gdx.prototype.g2d_get_pixels(pixmap);" +
"var pixmapAddr = Gdx.getPointer(pixmap);" +
"var format = pixmap.get_format();" +
"var width = pixmap.get_width();" +
"var height = pixmap.get_height();" +
"nativeData[0] = pixmapAddr;" +
"nativeData[1] = width;" +
"nativeData[2] = height;" +
"nativeData[3] = format;" +
"var bytesPerPixel = Gdx.Gdx.prototype.g2d_bytes_per_pixel(format);" +
"var bytesSize = width * height * bytesPerPixel;" +
"var startIndex = pixels;" +
"var endIndex = startIndex + bytesSize;" +
"var newArray = Gdx.HEAPU8.subarray(startIndex, endIndex);" +
"return newArray;"
"var nativeData = new Int32Array(6);" +
"nativeData[0] = pixmapAddr;" +
"nativeData[1] = width;" +
"nativeData[2] = height;" +
"nativeData[3] = format;" +
"nativeData[4] = startIndex;" +
"nativeData[5] = endIndex;" +
"return nativeData;"
)
private static native Uint8ArrayWrapper newPixmap(@JSByRef() int[] nativeData, int width, int height, int format); /*MANUAL
private static native Int32ArrayWrapper newPixmapNative(int width, int height, int format); /*MANUAL
gdx2d_pixmap* pixmap = gdx2d_new(width, height, format);
if(pixmap==0)
return 0;
Expand Down Expand Up @@ -438,4 +482,16 @@ static private String getFormatString(int format) {
@JSBody(params = { "msg" }, script = "" +
"console.log(msg);")
public static native void print(String msg);

@Override
public Int8ArrayWrapper recreateBuffer() {
Int8ArrayWrapper array = (Int8ArrayWrapper)getHeapData(false);
return array;
}

@Override
public void update() {
HasArrayBufferView hasArrayBufferView = (HasArrayBufferView)buffer;
hasArrayBufferView.setInt8ArrayNative(nativeBuffer);
}
}
Loading

0 comments on commit b323bee

Please sign in to comment.