From ebccecb4776fe707e073f0aba0c0b948ae45444e Mon Sep 17 00:00:00 2001 From: Michael Pollind Date: Sun, 9 May 2021 17:30:32 -0700 Subject: [PATCH] feature: add gestalt rendering core --- build.gradle | 2 + gestalt-graphics-core/build.gradle | 24 ++ .../terasology/gestalt/graphics/Color.java | 354 ++++++++++++++++++ .../terasology/gestalt/graphics/Colorc.java | 64 ++++ .../rendering/opengl/GLAttributes.java | 168 +++++++++ .../graphics/resource/IndexResource.java | 98 +++++ .../graphics/resource/VertexAttribute.java | 59 +++ .../resource/VertexAttributeBinding.java | 81 ++++ .../graphics/resource/VertexResource.java | 135 +++++++ .../resource/VertexResourceBuilder.java | 37 ++ .../gestalt/graphics/ColorTest.java | 296 +++++++++++++++ settings.gradle | 2 +- 12 files changed, 1319 insertions(+), 1 deletion(-) create mode 100644 gestalt-graphics-core/build.gradle create mode 100644 gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/Color.java create mode 100644 gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/Colorc.java create mode 100644 gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/rendering/opengl/GLAttributes.java create mode 100644 gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/IndexResource.java create mode 100644 gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexAttribute.java create mode 100644 gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexAttributeBinding.java create mode 100644 gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexResource.java create mode 100644 gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexResourceBuilder.java create mode 100644 gestalt-graphics-core/src/test/java/org/terasology/gestalt/graphics/ColorTest.java diff --git a/build.gradle b/build.gradle index f9d55c3f..94778f9d 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,8 @@ ext { slf4j_version = "1.7.25" gson_version = "2.8.5" jcip_annotation_version = "1.0" + joml_version = "1.10.0" + lwjgl_version = '3.2.3' // Testing junit_version = "4.12" diff --git a/gestalt-graphics-core/build.gradle b/gestalt-graphics-core/build.gradle new file mode 100644 index 00000000..247ef447 --- /dev/null +++ b/gestalt-graphics-core/build.gradle @@ -0,0 +1,24 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 +apply from: "$rootDir/gradle/common.gradle" + +// Primary dependencies definition +dependencies { + implementation "org.joml:joml:$joml_version" + implementation "com.google.guava:guava:$guava_version" + implementation 'com.googlecode.gentyref:gentyref:1.2.0' + implementation "org.slf4j:slf4j-api:$slf4j_version" + implementation "com.android.support:support-annotations:$android_annotation_version" + implementation "org.lwjgl:lwjgl:$lwjgl_version" + implementation "org.lwjgl:lwjgl-opengl:$lwjgl_version" + + // These dependencies are only needed for running tests + testImplementation "junit:junit:$junit_version" + testImplementation "ch.qos.logback:logback-classic:$logback_version" + testImplementation "org.mockito:mockito-core:$mockito_version" + + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.6.2") + testImplementation("org.junit.jupiter:junit-jupiter-api:5.6.2") + testImplementation("org.junit.jupiter:junit-jupiter-params:5.6.2") + testImplementation("junit:junit:4.12") +} diff --git a/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/Color.java b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/Color.java new file mode 100644 index 00000000..a6874fc1 --- /dev/null +++ b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/Color.java @@ -0,0 +1,354 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 +package org.terasology.gestalt.graphics; + +import org.joml.Math; +import org.joml.Vector3fc; +import org.joml.Vector3ic; +import org.joml.Vector4fc; +import org.joml.Vector4ic; + +import java.nio.ByteBuffer; +import java.util.Locale; +import java.util.Objects; + +/** + * Color is a representation of a RGBA color. Color components can be set and accessed via floats ranging from 0-1, or ints ranging from 0-255. + * Color is immutable and thread safe. + *

+ * There are a plethora of Color classes, but none that are quite suitable IMO: + * + * + */ +public class Color implements Colorc { + public static final Colorc black = new Color(0x000000FF); + public static final Colorc white = new Color(0xFFFFFFFF); + public static final Colorc blue = new Color(0x0000FFFF); + public static final Colorc green = new Color(0x00FF00FF); + public static final Colorc red = new Color(0xFF0000FF); + public static final Colorc grey = new Color(0x888888FF); + public static final Colorc transparent = new Color(0x00000000); + public static final Colorc yellow = new Color(0xFFFF00FF); + public static final Colorc cyan = new Color(0x00FFFFFF); + public static final Colorc magenta = new Color(0xFF00FFFF); + + + private static final int MAX = 255; + private static final int RED_OFFSET = 24; + private static final int GREEN_OFFSET = 16; + private static final int BLUE_OFFSET = 8; + private static final int RED_FILTER = 0x00FFFFFF; + private static final int GREEN_FILTER = 0xFF00FFFF; + private static final int BLUE_FILTER = 0xFFFF00FF; + private static final int ALPHA_FILTER = 0xFFFFFF00; + + private int representation; + + /** + * Creates a color that is black with full alpha. + */ + public Color() { + representation = 0x000000FF; + } + + /** + * range between 0x00000000 to 0xFFFFFFFF + * + * @param representation color in hex format + */ + public Color(int representation) { + this.representation = representation; + } + + /** + * set the color source + * + * @param src color source + */ + public Color(Colorc src) { + this.set(src.rgba()); + } + + /** + * Create a color with the given red/green/blue values. Alpha is initialised as max. + * + * @param r red in the range of 0.0f to 1.0f + * @param g green in the range of 0.0f to 1.0f + * @param b blue in the range of 0.0f to 1.0f + */ + public Color(float r, float g, float b) { + this((int) (r * MAX), (int) (g * MAX), (int) (b * MAX)); + } + + /** + * Creates a color with the given red/green/blue/alpha values. + * + * @param r red in the range of 0.0f to 1.0f + * @param g green in the range of 0.0f to 1.0f + * @param b blue in the range of 0.0f to 1.0f + * @param a alpha in the range of 0.0f to 1.0f + */ + public Color(float r, float g, float b, float a) { + this((int) (r * MAX), (int) (g * MAX), (int) (b * MAX), (int) (a * MAX)); + } + + /** + * Creates a color with the given red/green/blue values. Alpha is initialised as max. + * + * @param r red in the range of 0.0f to 1.0f + * @param g green in the range of 0.0f to 1.0f + * @param b blue in the range of 0.0f to 1.0f + */ + public Color(int r, int g, int b) { + this.set(r, g, b); + } + + /** + * Creates a color with the given red/green/blue/alpha values. + * + * @param r red in the range of 0 to 255 + * @param g green in the range of 0 to 255 + * @param b blue in the range of 0 to 255 + * @param a alpha in the range of 0 to 255 + */ + public Color(int r, int g, int b, int a) { + this.set(r, g, b, a); + } + + @Override + public int r() { + return (representation >> RED_OFFSET) & MAX; + } + + @Override + public int g() { + return (representation >> GREEN_OFFSET) & MAX; + } + + @Override + public int b() { + return (representation >> BLUE_OFFSET) & MAX; + } + + @Override + public int a() { + return representation & MAX; + } + + @Override + public float rf() { + return r() / 255.f; + } + + @Override + public float bf() { + return b() / 255.f; + } + + @Override + public float gf() { + return g() / 255.f; + } + + @Override + public float af() { + return a() / 255.f; + } + + + public Color set(Vector3ic representation) { + return this.set(representation.x(), + representation.y(), + representation.z()); + } + + public Color set(Vector3fc representation) { + return this.set((int) (representation.x() * MAX), + (int) (representation.y() * MAX), + (int) (representation.z() * MAX)); + } + + + public Color set(Vector4fc representation) { + return this.set((int) (representation.x() * MAX), + (int) (representation.y() * MAX), + (int) (representation.z() * MAX), + (int) (representation.w() * MAX)); + } + + public Color set(Vector4ic representation) { + return this.set(representation.x(), + representation.y(), + representation.z(), + representation.w()); + } + + public Color set(int representation) { + this.representation = representation; + return this; + } + + public Color set(int r, int g, int b, int a) { + return this.set(Math.clamp(0, 255, r) << RED_OFFSET | + Math.clamp(0, 255, g) << GREEN_OFFSET | + Math.clamp(0, 255, b) << BLUE_OFFSET | + Math.clamp(0, 255, a)); + } + + + public Color set(int r, int g, int b) { + return this.set(r, g, b, 0xFF); + } + + + /** + * set the value of the red channel + * + * @param value color range between 0-255 + * @return this + */ + public Color setRed(int value) { + return this.set(Math.clamp(0, 255, value) << RED_OFFSET | (representation & RED_FILTER)); + } + + /** + * set the value of the red channel + * + * @param value color range between 0.0f to 1.0f + * @return this + */ + public Color setRed(float value) { + return setRed((int) (value * MAX)); + } + + /** + * set the value of the green channel + * + * @param value color range between 0-255 + * @return this + */ + public Color setGreen(int value) { + return this.set(Math.clamp(0, 255, value) << GREEN_OFFSET | (representation & GREEN_FILTER)); + } + + + /** + * set the value of the green channel + * + * @param value color range between 0.0f to 1.0f + * @return this + */ + public Color setGreen(float value) { + return setGreen((int) (value * MAX)); + } + + + /** + * set the value of the blue channel + * + * @param value blue range between 0-255 + * @return this + */ + public Color setBlue(int value) { + return this.set(Math.clamp(0, 255, value) << BLUE_OFFSET | (representation & BLUE_FILTER)); + } + + /** + * set the value of the blue channel + * + * @param value blue range between 0.0f to 1.0f + * @return this + */ + public Color setBlue(float value) { + return setBlue((int) (value * MAX)); + } + + /** + * set the value of the alpha channel + * + * @param value alpha range between 0-255 + * @return this + */ + public Color setAlpha(int value) { + return this.set(Math.clamp(0, 255, value) | (representation & ALPHA_FILTER)); + } + + /** + * set the value of the alpha channel + * + * @param value alpha range between 0.0f to 1.0f + * @return this + */ + public Color setAlpha(float value) { + return setAlpha((int) (value * MAX)); + } + + + /** + * 255 Subtract from all components except alpha; + * + * @return this + */ + public Color invert() { + return this.set((~representation & ALPHA_FILTER) | a()); + } + + @Override + public int rgba() { + return representation; + } + + @Override + public int rgb() { + return (representation & ALPHA_FILTER) | 0xFF; + } + + /** + * write color to ByteBuffer as int. + * + * @param buffer The ByteBuffer + */ + public void addToBuffer(ByteBuffer buffer) { + buffer.putInt(representation); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj instanceof Color) { + Color other = (Color) obj; + return representation == other.representation; + } + return false; + } + + @Override + public int hashCode() { + return Objects.hash(representation); + } + + @Override + public String toHex() { + StringBuilder builder = new StringBuilder(); + String hexString = Integer.toHexString(representation); + for (int i = 0; i < 8 - hexString.length(); ++i) { + builder.append('0'); + } + builder.append(hexString.toUpperCase(Locale.ENGLISH)); + return builder.toString(); + } + + @Override + public String toString() { + return toHex(); + } +} diff --git a/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/Colorc.java b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/Colorc.java new file mode 100644 index 00000000..a11f85ab --- /dev/null +++ b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/Colorc.java @@ -0,0 +1,64 @@ +package org.terasology.gestalt.graphics; + +/** + * Interface to a read-only view of a Color. + */ +public interface Colorc { + /** + * The internal representation of the color + * @return hex representation + */ + int rgba(); + + /** + * the internal representation of the color with alpha channel set to 0xFF + * @return hex representation + */ + int rgb(); + + /** + * @return The red component, between 0 and 255 + */ + int r(); + + /** + * @return The green component, between 0 and 255 + */ + int g(); + + /** + * @return The blue component, between 0 and 255 + */ + int b(); + + /** + * @return The alpha component, between 0 and 255 + */ + int a(); + + /** + * @return The red channel, between 0.0f and 1.0f + */ + float rf(); + + /** + * @return The green channel, between 0.0f and 1.0f + */ + float gf(); + + /** + * @return The blue channel, between 0.0f and 1.0f + */ + float bf(); + + /** + * @return The alpha channel, between 0.0f and 1.0f + */ + float af(); + + /** + * the hex representation of color as a String + * @return the hex representation + */ + String toHex(); +} diff --git a/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/rendering/opengl/GLAttributes.java b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/rendering/opengl/GLAttributes.java new file mode 100644 index 00000000..9d71fd49 --- /dev/null +++ b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/rendering/opengl/GLAttributes.java @@ -0,0 +1,168 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.gestalt.graphics.rendering.opengl; + +import org.joml.Vector2f; +import org.joml.Vector2fc; +import org.joml.Vector3f; +import org.joml.Vector3fc; +import org.joml.Vector4f; +import org.joml.Vector4fc; +import org.terasology.gestalt.graphics.Color; +import org.terasology.gestalt.graphics.Colorc; +import org.terasology.gestalt.graphics.resource.VertexAttribute; +import org.terasology.gestalt.graphics.resource.VertexResource; + +import java.nio.ByteBuffer; + +public final class GLAttributes { + private GLAttributes() { + + } + + public static final VertexAttribute VECTOR_3_F_VERTEX_ATTRIBUTE = new VertexAttribute(Vector3f.class, new VertexAttribute.AttributeConfiguration() { + + @Override + public void write(Vector3fc value, int vertIdx, int offset, VertexResource resource) { + int bufferStart = vertIdx * resource.getInStride() + offset; + ByteBuffer buffer = resource.buffer(); + buffer.putFloat(bufferStart, value.x()); + buffer.putFloat(bufferStart + Float.BYTES, value.y()); + buffer.putFloat(bufferStart + Float.BYTES * 2, value.z()); + } + + @Override + public Vector3f read(int vertIdx, int offset, VertexResource resource, Vector3f dest) { + int bufferStart = vertIdx * resource.getInStride() + offset; + ByteBuffer buffer = resource.buffer(); + dest.x = buffer.getFloat(bufferStart); + dest.y = buffer.getFloat(bufferStart + Float.BYTES); + dest.z = buffer.getFloat(bufferStart + Float.BYTES * 2); + return dest; + } + + @Override + public int size(int vertIdx, int offset, VertexResource resource) { + return (vertIdx * resource.getInStride() + offset) + Float.BYTES * 3; + } + + @Override + public int numElements(int offset, VertexResource resource) { + int size = (resource.getInSize() / resource.getInStride()); + if (resource.getInSize() % resource.getInStride() >= Float.BYTES * 3) { + size++; + } + return size; + } + }, VertexAttribute.TypeMapping.ATTR_FLOAT, 3); + + public static final VertexAttribute VECTOR_4_F_VERTEX_ATTRIBUTE = new VertexAttribute<>(Vector4f.class, new VertexAttribute.AttributeConfiguration() { + @Override + public void write(Vector4fc value, int vertIdx, int offset, VertexResource resource) { + int bufferStart = vertIdx * resource.getInStride() + offset; + ByteBuffer buffer = resource.buffer(); + buffer.putFloat(bufferStart, value.x()); + buffer.putFloat(bufferStart + Float.BYTES, value.y()); + buffer.putFloat(bufferStart + Float.BYTES * 2, value.z()); + buffer.putFloat(bufferStart + Float.BYTES * 3, value.w()); + } + + @Override + public Vector4f read(int vertIdx, int offset, VertexResource resource, Vector4f dest) { + int bufferStart = vertIdx * resource.getInStride() + offset; + ByteBuffer buffer = resource.buffer(); + dest.x = buffer.getFloat(bufferStart); + dest.y = buffer.getFloat(bufferStart + Float.BYTES); + dest.z = buffer.getFloat(bufferStart + Float.BYTES * 2); + dest.w = buffer.getFloat(bufferStart + Float.BYTES * 3); + return dest; + } + + @Override + public int size(int vertIdx, int offset, VertexResource resource) { + return (vertIdx * resource.getInStride() + offset) + Float.BYTES * 4; + } + + @Override + public int numElements(int offset, VertexResource resource) { + int size = (resource.getInSize() / resource.getInStride()); + if (resource.getInSize() % resource.getInStride() >= Float.BYTES * 4) { + size++; + } + return size; + } + }, VertexAttribute.TypeMapping.ATTR_FLOAT, 4); + + public static final VertexAttribute COLOR_4_F_VERTEX_ATTRIBUTE = new VertexAttribute(Color.class, new VertexAttribute.AttributeConfiguration() { + @Override + public void write(Colorc value, int vertIdx, int offset, VertexResource resource) { + int bufferStart = vertIdx * resource.getInStride() + offset; + ByteBuffer buffer = resource.buffer(); + + buffer.putFloat(bufferStart, value.rf()); + buffer.putFloat(bufferStart + Float.BYTES, value.gf()); + buffer.putFloat(bufferStart + Float.BYTES * 2, value.bf()); + buffer.putFloat(bufferStart + Float.BYTES * 3, value.af()); + } + + @Override + public Color read(int vertIdx, int offset, VertexResource resource, Color dest) { + int bufferStart = vertIdx * resource.getInStride() + offset; + ByteBuffer buffer = resource.buffer(); + dest.setRed(buffer.getFloat(bufferStart)); + dest.setGreen(buffer.getFloat(bufferStart + Float.BYTES)); + dest.setBlue(buffer.getFloat(bufferStart + Float.BYTES * 2)); + dest.setAlpha(buffer.getFloat(bufferStart + Float.BYTES * 3)); + return dest; + } + + @Override + public int size(int vertIdx, int offset, VertexResource resource) { + return (vertIdx * resource.getInStride() + offset) + Float.BYTES * 4; + } + + @Override + public int numElements(int offset, VertexResource resource) { + int size = (resource.getInSize() / resource.getInStride()); + if (resource.getInSize() % resource.getInStride() >= (offset + Float.BYTES * 4)) { + size++; + } + return size; + } + + }, VertexAttribute.TypeMapping.ATTR_FLOAT, 4); + + public static final VertexAttribute VECTOR_2_F_VERTEX_ATTRIBUTE = new VertexAttribute<>(Vector2f.class, new VertexAttribute.AttributeConfiguration() { + @Override + public void write(Vector2fc value, int vertIdx, int offset, VertexResource resource) { + int bufferStart = vertIdx * resource.getInStride() + offset; + ByteBuffer buffer = resource.buffer(); + buffer.putFloat(bufferStart, value.x()); + buffer.putFloat(bufferStart + Float.BYTES, value.y()); + } + + @Override + public Vector2f read(int vertIdx, int offset, VertexResource resource, Vector2f dest) { + int bufferStart = vertIdx * resource.getInStride() + offset; + ByteBuffer buffer = resource.buffer(); + dest.x = buffer.getFloat(bufferStart); + dest.y = buffer.getFloat(bufferStart + Float.BYTES); + return dest; + } + + @Override + public int size(int vertIdx, int offset, VertexResource resource) { + return (vertIdx * resource.getInStride() + offset) + Float.BYTES * 2; + } + + @Override + public int numElements(int offset, VertexResource resource) { + int size = (resource.getInSize() / resource.getInStride()); + if (resource.getInSize() % resource.getInStride() >= Float.BYTES * 2) { + size++; + } + return size; + } + }, VertexAttribute.TypeMapping.ATTR_FLOAT, 2); +} diff --git a/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/IndexResource.java b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/IndexResource.java new file mode 100644 index 00000000..719920ee --- /dev/null +++ b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/IndexResource.java @@ -0,0 +1,98 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.gestalt.graphics.resource; + +import org.lwjgl.BufferUtils; + +import java.nio.ByteBuffer; + +/** + * defines the order of vertices to walk for rendering geometry + * + * refrence: https://www.khronos.org/opengl/wiki/Primitive + */ +public class IndexResource { + public ByteBuffer buffer; + private int numIndices = 0; + private int inSize = 0; + private int posIndex = 0; + + public int getNumberOfIndices() { + return numIndices; + } + + public int getSize() { + return numIndices * Integer.BYTES; + } + + public IndexResource() { + this.buffer = BufferUtils.createByteBuffer(0); + } + + public void ensureCapacity(int size) { + if (size > buffer.capacity()) { + int newCap = Math.max(this.inSize << 1, size); + ByteBuffer newBuffer = BufferUtils.createByteBuffer(newCap); + buffer.limit(Math.min(size, inSize)); + buffer.position(0); + newBuffer.put(buffer); + this.buffer = newBuffer; + } + if (size > this.inSize) { + this.inSize = size; + } + } + + public void copy(IndexResource resource) { + ensureCapacity(resource.inSize); + ByteBuffer copyBuffer = resource.buffer; + copyBuffer.limit(resource.getSize()); + copyBuffer.rewind(); + buffer.put(copyBuffer); + + this.inSize = resource.inSize; + this.numIndices = resource.getNumberOfIndices(); + } + + + public void reserveElements(int elements) { + ensureCapacity(elements * Integer.BYTES); + } + + public void rewind() { + posIndex = 0; + } + + public void put(int value) { + ensureCapacity((posIndex + 1) * Integer.BYTES); + buffer.putInt(posIndex * Integer.BYTES, value); + posIndex++; + if (posIndex > numIndices) { + numIndices = posIndex; + } + } + + public void squeeze() { + if (this.inSize != buffer.capacity()) { + ByteBuffer newBuffer = BufferUtils.createByteBuffer(this.inSize); + buffer.limit(this.inSize); + buffer.position(0); + newBuffer.put(buffer); + this.buffer = newBuffer; + } + } + + public void reallocateElements(int indices) { + ensureCapacity((indices + 1) * Integer.BYTES); + numIndices = indices; + } + + public void position(int position) { + posIndex = position; + } + + public void put(int index, int value) { + buffer.putInt(index * Integer.BYTES, value); + } +} diff --git a/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexAttribute.java b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexAttribute.java new file mode 100644 index 00000000..84b0741c --- /dev/null +++ b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexAttribute.java @@ -0,0 +1,59 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.gestalt.graphics.resource; + + +import org.lwjgl.opengl.GL30; + +/** + * attribute maps a target object or a primitive data to a {@link VertexResource} + * + * @param the target object + */ +public class VertexAttribute { + + public final TypeMapping mapping; + public final int count; + public final Class type; + public final AttributeConfiguration configuration; + + public interface AttributeConfiguration { + void write(T value, int vertIdx, int offset, VertexResource resource); + + TImpl read(int vertIdx, int offset, VertexResource resource, TImpl dest); + + int size(int vertIdx, int offset, VertexResource resource); + + int numElements(int offset, VertexResource resource); + } + + /** + * @param type the mapping type + * @param mapping maps a primitive to a given supported type. + * @param count the number elements that is described by the target + */ + public VertexAttribute(Class type, AttributeConfiguration attributeConfiguration, TypeMapping mapping, int count) { + this.type = type; + this.mapping = mapping; + this.count = count; + this.configuration = attributeConfiguration; + + } + + + public enum TypeMapping { + ATTR_FLOAT(Float.BYTES, GL30.GL_FLOAT), + ATTR_SHORT(Short.BYTES, GL30.GL_SHORT), + ATTR_BYTE(Byte.BYTES, GL30.GL_BYTE), + ATTR_INT(Integer.BYTES, GL30.GL_INT); + + public final int size; + public final int glType; + + TypeMapping(int size, int glType) { + this.size = size; + this.glType = glType; + } + } +} diff --git a/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexAttributeBinding.java b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexAttributeBinding.java new file mode 100644 index 00000000..0cf483d5 --- /dev/null +++ b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexAttributeBinding.java @@ -0,0 +1,81 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.gestalt.graphics.resource; + +/** + * a binding that maps depending on the type of attribute and a resource where the data is committed to + * @param + */ +public class VertexAttributeBinding { + private final VertexResource resource; + private final VertexAttribute attribute; + private final int offset; + private int vertexIndex; + + private int version = -1; + private int numberElements = 0; + + public VertexAttributeBinding(VertexResource resource, int offset, VertexAttribute attribute) { + this.resource = resource; + this.attribute = attribute; + this.offset = offset; + } + + public VertexResource getResource() { + return resource; + } + + public void reserve(int vertCount) { + resource.ensureCapacity(attribute.configuration.size(vertCount, this.offset, resource)); + } + + public void allocate(int elements) { + this.resource.reserveElements(elements); + } + + public void rewind() { + this.vertexIndex = 0; + } + + public void setPosition(int index) { + this.vertexIndex = index; + } + + public int numberOfElements() { + if (version != resource.getVersion()) { + update(); + } + return numberElements; + } + + private void update() { + if (resource.getInSize() == 0 || resource.getInStride() == 0) { + numberElements = 0; + } else { + numberElements = attribute.configuration.numElements(offset, resource); + } + this.version = resource.getVersion(); + } + + /** + * write a value by the index. + * + * @param value the value to commit + */ + public void put(T value) { + resource.ensureCapacity(attribute.configuration.size(this.vertexIndex, this.offset, resource)); + attribute.configuration.write(value, this.vertexIndex, this.offset, resource); + this.vertexIndex++; + this.resource.mark(); + } + + public void set(int index, T value) { + attribute.configuration.write(value, index, this.offset, resource); + this.resource.mark(); + } + + public TImpl get(int index, TImpl dest) { + return attribute.configuration.read(index, this.offset, resource, dest); + } +} diff --git a/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexResource.java b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexResource.java new file mode 100644 index 00000000..4d3aceba --- /dev/null +++ b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexResource.java @@ -0,0 +1,135 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.gestalt.graphics.resource; + +import org.lwjgl.BufferUtils; + +import java.nio.ByteBuffer; + +public class VertexResource { + private int inStride = 0; + private int inSize = 0; + private int version = 0; + private ByteBuffer buffer = BufferUtils.createByteBuffer(0); + private VertexDefinition[] attributes; + + public int inSize() { + return inSize; + } + + public VertexResource() { + + } + + public VertexDefinition[] definitions() { + return this.attributes; + } + + public ByteBuffer buffer() { + return this.buffer; + } + + public void setDefinitions(VertexDefinition[] attr) { + this.attributes = attr; + } + + public VertexResource(int inSize, int inStride, VertexDefinition[] attributes) { + this.inStride = inStride; + this.inSize = inSize; + this.attributes = attributes; + this.buffer = BufferUtils.createByteBuffer(inSize); + } + + public void copy(VertexResource resource) { + if (resource.inSize == 0) { + return; + } + ensureCapacity(resource.inSize); + ByteBuffer copyBuffer = resource.buffer; + copyBuffer.limit(resource.inSize()); + copyBuffer.rewind(); + buffer.put(copyBuffer); + + this.inSize = resource.inSize; + this.inStride = resource.inStride; + this.mark(); + } + + public int getInSize() { + return inSize; + } + + public int getInStride() { + return inStride; + } + + public void ensureCapacity(int size) { + if (size > buffer.capacity()) { + int newCap = Math.max(this.inSize << 1, size); + ByteBuffer newBuffer = BufferUtils.createByteBuffer(newCap); + buffer.limit(Math.min(size, this.inSize)); + buffer.position(0); + newBuffer.put(buffer); + this.buffer = newBuffer; + } + if (size > this.inSize) { + this.inSize = size; + } + } + + public void reserveElements(int verts) { + int size = verts * inStride; + ensureCapacity(size); + } + + public void reallocateElements(int verts) { + int size = verts * inStride; + ensureCapacity(size); + this.inSize = size; + squeeze(); + + } + + public void squeeze() { + if (this.inSize != buffer.capacity()) { + ByteBuffer newBuffer = BufferUtils.createByteBuffer(this.inSize); + buffer.limit(this.inSize); + buffer.position(0); + newBuffer.put(buffer); + this.buffer = newBuffer; + } + } + + public void allocate(int size, int stride) { + this.ensureCapacity(size); + this.inStride = stride; + this.inSize = size; + } + + public int getVersion() { + return version; + } + + /** + * increase version flag for change + */ + public void mark() { + version++; + } + + /** + * describes the metadata and placement into the buffer based off the stride. + */ + public static class VertexDefinition { + public final int location; + public final VertexAttribute attribute; + public final int offset; + + public VertexDefinition(int location, int offset, VertexAttribute attribute) { + this.location = location; + this.attribute = attribute; + this.offset = offset; + } + } +} diff --git a/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexResourceBuilder.java b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexResourceBuilder.java new file mode 100644 index 00000000..a5877666 --- /dev/null +++ b/gestalt-graphics-core/src/main/java/org/terasology/gestalt/graphics/resource/VertexResourceBuilder.java @@ -0,0 +1,37 @@ +// Copyright 2021 The Terasology Foundation +// SPDX-License-Identifier: Apache-2.0 + +package org.terasology.gestalt.graphics.resource; + +import java.util.ArrayList; +import java.util.List; + +public class VertexResourceBuilder { + private List definitions = new ArrayList<>(); + private int inStride; + private VertexResource resource = new VertexResource(); + + public VertexResourceBuilder() { + } + + /** + * add an attribute and provides an {@link VertexAttributeBinding} + * + * @param location the index of the attribute binding + * @param attribute the attribute that describes the binding + * @param + * @return + */ + public VertexAttributeBinding add(int location, VertexAttribute attribute) { + VertexAttributeBinding result = new VertexAttributeBinding(resource, inStride, attribute); + this.definitions.add(new VertexResource.VertexDefinition(location, inStride, attribute)); + inStride += attribute.mapping.size * attribute.count; + return result; + } + + public VertexResource build() { + resource.setDefinitions(definitions.toArray(new VertexResource.VertexDefinition[]{})); + resource.allocate(0, inStride); + return resource; + } +} diff --git a/gestalt-graphics-core/src/test/java/org/terasology/gestalt/graphics/ColorTest.java b/gestalt-graphics-core/src/test/java/org/terasology/gestalt/graphics/ColorTest.java new file mode 100644 index 00000000..8f949915 --- /dev/null +++ b/gestalt-graphics-core/src/test/java/org/terasology/gestalt/graphics/ColorTest.java @@ -0,0 +1,296 @@ +package org.terasology.gestalt.graphics; + +import org.joml.Vector3f; +import org.joml.Vector4f; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class ColorTest { + + static Stream singleColorChangeIntArgs() { + return Stream.of( + Arguments.of(255, 255), + Arguments.of(175, 175), + Arguments.of(125, 125), + Arguments.of(600, 255), + Arguments.of(0, 0) + ); + }; + + static Stream setColorVector4fArgs() { + return Stream.of( + Arguments.of(new Vector4f(1.0f,0,0,0), new Color(255, 0,0,0)), + Arguments.of(new Vector4f(0,1.0f,0,0), new Color(0, 255,0,0)), + Arguments.of(new Vector4f(0,0,1.0f,0.0f), new Color(0, 0,255,0)), + Arguments.of(new Vector4f(0,0,0.0f,1.0f), new Color(0, 0,0,255)) + ); + } + + static Stream setColorVector3fArgs() { + return Stream.of( + Arguments.of(new Vector3f(1.0f,0,0), new Color(255, 0,0,255)), + Arguments.of(new Vector3f(0,1.0f,0), new Color(0, 255,0,255)), + Arguments.of(new Vector3f(0,0,1.0f), new Color(0, 0,255,255)) + ); + } + + static Stream SingleColorChangeFloatArgs() { + return Stream.of( + Arguments.of(1.0f, 255), + Arguments.of(0.68f, 173), + Arguments.of(0.49f, 124), + Arguments.of(6.0f, 255), + Arguments.of(-1.0f, 0), + Arguments.of(0, 0) + ); + }; + + static Stream inverseColorArgs() { + return Stream.of( + Arguments.of(new Color(255, 0, 0, 255), new Color(0, 255, 255, 255)), + Arguments.of(new Color(0, 125, 0, 255), new Color(255, 130, 255, 255)), + Arguments.of(new Color(125, 125, 0, 255), new Color(130, 130, 255, 255)), + Arguments.of(new Color(0, 0, 90, 255), new Color(255, 255, 165, 255)) + ); + }; + + static Stream hexColorArgs() { + return Stream.of( + Arguments.of(new Color(255, 0, 0, 255),"FF0000FF"), + Arguments.of(new Color(0, 125, 0, 255), "007D00FF"), + Arguments.of(new Color(125, 125, 0, 255), "7D7D00FF"), + Arguments.of(new Color(0, 0, 90, 255), "00005AFF") + ); + }; + + + @ParameterizedTest + @MethodSource("hexColorArgs") + public void testHexColor(Color c, String expected) { + Color c1 = new Color(c); + assertEquals(c1.toHex(), expected); + } + + + @ParameterizedTest + @MethodSource("singleColorChangeIntArgs") + public void testSetColorR(int test, int expected) { + Color c1 = new Color(); + c1.setRed(test); + + assertEquals(expected, c1.r()); + assertEquals(0, c1.g()); + assertEquals(0, c1.b()); + assertEquals(255, c1.a()); + } + + + @ParameterizedTest + @MethodSource("singleColorChangeIntArgs") + public void testSetColorG(int value, int expected) { + Color c1 = new Color(); + c1.setGreen(value); + + assertEquals(0, c1.r()); + assertEquals(expected, c1.g()); + assertEquals(0, c1.b()); + assertEquals(255, c1.a()); + } + + @ParameterizedTest + @MethodSource("singleColorChangeIntArgs") + public void testSetColorB(int value, int expected) { + Color c1 = new Color(); + c1.setBlue(value); + + assertEquals(0, c1.r()); + assertEquals(0, c1.g()); + assertEquals(expected, c1.b()); + assertEquals(255, c1.a()); + + } + + @ParameterizedTest + @MethodSource("singleColorChangeIntArgs") + public void testSetColorA(int value, int expected) { + Color c1 = new Color(); + c1.setAlpha(value); + + assertEquals(0, c1.r()); + assertEquals(0, c1.g()); + assertEquals(0, c1.b()); + assertEquals(expected, c1.a()); + } + + @ParameterizedTest + @MethodSource("singleColorChangeIntArgs") + public void testIntColorR(int value, int expected) { + Color c1 = new Color(value, 0, 0); + + assertEquals(expected, c1.r()); + assertEquals(0, c1.g()); + assertEquals(0, c1.b()); + assertEquals(255, c1.a()); + } + + @ParameterizedTest + @MethodSource("singleColorChangeIntArgs") + public void testIntColorG(int value, int expected) { + Color c2 = new Color(0, value, 0); + + assertEquals(c2.r(), 0); + assertEquals(c2.g(), expected); + assertEquals(c2.b(), 0); + assertEquals(c2.a(), 255); + } + + @ParameterizedTest + @MethodSource("singleColorChangeIntArgs") + public void testIntColorB(int value, int expected) { + Color c3 = new Color(0, 0, value); + + assertEquals(c3.r(), 0); + assertEquals(c3.g(), 0); + assertEquals(c3.b(), expected); + assertEquals(c3.a(), 255); + } + + @ParameterizedTest + @MethodSource("singleColorChangeIntArgs") + public void testIntColorA(int value, int expected) { + Color c3 = new Color(0, 0, 0,value); + + assertEquals(c3.r(), 0); + assertEquals(c3.g(), 0); + assertEquals(c3.b(), 0); + assertEquals(c3.a(), expected); + } + + @ParameterizedTest + @MethodSource("SingleColorChangeFloatArgs") + public void testFloatColorR(float value, int expected) { + Color c2 = new Color(value, 0, 0); + + assertEquals(c2.r(), expected); + assertEquals(c2.g(), 0); + assertEquals(c2.b(), 0); + assertEquals(c2.a(), 255); + } + + @ParameterizedTest + @MethodSource("SingleColorChangeFloatArgs") + public void testFloatColorG(float value, int expected) { + Color c2 = new Color(0, value, 0); + + assertEquals(c2.r(), 0); + assertEquals(c2.g(), expected); + assertEquals(c2.b(), 0); + assertEquals(c2.a(), 255); + } + + @ParameterizedTest + @MethodSource("SingleColorChangeFloatArgs") + public void testFloatColorB(float value, int expected) { + Color c2 = new Color(0, 0.0f, value); + + assertEquals(c2.r(), 0); + assertEquals(c2.g(), 0); + assertEquals(c2.b(), expected); + assertEquals(c2.a(), 255); + } + + + @ParameterizedTest + @MethodSource("SingleColorChangeFloatArgs") + public void testSetFloatColorA(float value, int expected) { + Color test = new Color(); + test.setAlpha(value); + + assertEquals(test.r(), 0); + assertEquals(test.g(), 0); + assertEquals(test.b(), 0); + assertEquals(test.a(), expected); + } + + @ParameterizedTest + @MethodSource("SingleColorChangeFloatArgs") + public void testSetFloatColorR(float value, int expected) { + Color test = new Color(); + test.setRed(value); + + assertEquals(test.r(), expected); + assertEquals(test.g(), 0); + assertEquals(test.b(), 0); + assertEquals(test.a(), 255); + } + + @ParameterizedTest + @MethodSource("SingleColorChangeFloatArgs") + public void testSetFloatColorG(float value, int expected) { + Color c2 = new Color(); + c2.setGreen(value); + + assertEquals(c2.r(), 0); + assertEquals(c2.g(), expected); + assertEquals(c2.b(), 0); + assertEquals(c2.a(), 255); + } + + @ParameterizedTest + @MethodSource("SingleColorChangeFloatArgs") + public void testSetFloatColorB(float value, int expected) { + Color c2 = new Color(); + c2.setBlue(value); + + assertEquals(c2.r(), 0); + assertEquals(c2.g(), 0); + assertEquals(c2.b(), expected); + assertEquals(c2.a(), 255); + } + + + @ParameterizedTest + @MethodSource("SingleColorChangeFloatArgs") + public void testFloatColorA(float value, int expected) { + Color test = new Color(); + test.setAlpha(value); + + assertEquals(test.r(), 0); + assertEquals(test.g(), 0); + assertEquals(test.b(), 0); + assertEquals(test.a(), expected); + } + + + + @ParameterizedTest + @MethodSource("inverseColorArgs") + public void testInvert(Color value, Color expected) { + Color test = new Color(value); + test.invert(); + assertEquals(expected, test); + } + + + @ParameterizedTest + @MethodSource("setColorVector4fArgs") + public void testSetColorVector4f(Vector4f pos, Color expected) { + Color test = new Color(); + test.set(pos); + assertEquals(test, expected); + } + + @ParameterizedTest + @MethodSource("setColorVector3fArgs") + public void testSetColorVector3f(Vector3f pos, Color expected) { + Color c = new Color(); + c.set(pos); + assertEquals(c, expected); + } +} + diff --git a/settings.gradle b/settings.gradle index dbcf025b..b841ed1b 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,5 +1,5 @@ rootProject.name = 'gestalt' -include 'gestalt-util', 'gestalt-di', 'gestalt-inject-java', 'gestalt-inject', 'gestalt-annotation', 'testpack:testpack-api', 'gestalt-module', 'testpack:moduleA', 'testpack:moduleB', 'testpack:moduleC', 'testpack:moduleD', 'testpack:moduleF', 'gestalt-asset-core', 'gestalt-entity-system', 'gestalt-es-perf' +include 'gestalt-graphics-core', 'gestalt-util', 'gestalt-di', 'gestalt-inject-java', 'gestalt-inject', 'gestalt-annotation', 'testpack:testpack-api', 'gestalt-module', 'testpack:moduleA', 'testpack:moduleB', 'testpack:moduleC', 'testpack:moduleD', 'testpack:moduleF', 'gestalt-asset-core', 'gestalt-entity-system', 'gestalt-es-perf' if (rootProject.projectDir.toPath().resolve("local.properties").toFile().exists()) { include 'gestalt-android', 'gestalt-android-testbed' } else {