More work done.

- Added: New Window System (old one ported to the new system)
- Added: More support for textures
- Added: Compute Pipeline
- Added: Fences & Queries
- Added: More Support for vulkan (Buffers now define the inputted buffer
ownership)
This commit is contained in:
2026-07-30 14:02:05 +02:00
parent bfa53c7849
commit cdcbd92f73
62 changed files with 3335 additions and 415 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
org.gradle.jvmargs=-Xmx2G
lwjglVersion = 3.4.2
lwjglVersion = 3.3.4
lwjglNatives = natives-windows
@@ -4,11 +4,13 @@ import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.util.List;
import org.jspecify.annotations.NonNull;
import org.lwjgl.system.MemoryUtil;
import speiger.src.collections.ints.misc.pairs.IntObjectPair;
import speiger.src.coreengine.graphics.api.buffer.states.BufferState;
import speiger.src.coreengine.graphics.api.buffer.states.BufferType;
import speiger.src.coreengine.graphics.api.utils.BufferOwner;
import speiger.src.coreengine.graphics.api.utils.GraphicsResource;
public abstract class VertexBuffer implements GraphicsResource {
@@ -37,30 +39,18 @@ public abstract class VertexBuffer implements GraphicsResource {
public abstract VertexBuffer unbind();
public abstract VertexBuffer allocate(int totalBytes);
public abstract VertexBuffer set(long pointer, int totalBytes);
public VertexBuffer set(ByteBuffer buffer) { return set(MemoryUtil.memAddress(buffer), buffer.remaining()); }
public VertexBuffer set(byte[] data) {
ByteBuffer buffer = MemoryUtil.memAlloc(data.length).put(data).flip();
set(buffer);
MemoryUtil.memFree(buffer);
return this;
}
public abstract VertexBuffer set(long pointer, int totalBytes, BufferOwner owner);
public VertexBuffer set(ByteBuffer buffer, BufferOwner owner) { return set(MemoryUtil.memAddress(buffer), buffer.remaining(), owner); }
public VertexBuffer set(byte[] data) { return set(MemoryUtil.memAlloc(data.length).put(data).flip(), BufferOwner.GIVEN); }
public abstract VertexBuffer fill(long pointer, int totalBytes, int offset);
public VertexBuffer fill(int offset, ByteBuffer buffer) { return fill(MemoryUtil.memAddress(buffer), buffer.remaining(), offset); }
public abstract VertexBuffer fill(long pointer, int totalBytes, int offset, BufferOwner owner);
public VertexBuffer fill(int offset, ByteBuffer buffer, BufferOwner owner) { return fill(MemoryUtil.memAddress(buffer), buffer.remaining(), offset, owner); }
public VertexBuffer fill(int offset, byte[] data) {
ByteBuffer buffer = MemoryUtil.memAlloc(data.length).put(data).flip();
fill(offset, buffer);
MemoryUtil.memFree(buffer);
return this;
return fill(offset, MemoryUtil.memAlloc(data.length).put(data).flip(), BufferOwner.GIVEN);
}
public abstract VertexBuffer read(long pointer, int totalBytes, int offset);
public VertexBuffer read(Buffer buffer, int totalBytes, int offset) {
read(MemoryUtil.memAddress(buffer), totalBytes, offset);
buffer.flip();
return this;
}
public abstract VertexBuffer read(long pointer, int totalBytes, int offset, @NonNull Runnable completion);
public VertexBuffer read(Buffer buffer, int totalBytes, int offset, @NonNull Runnable completion) { return read(MemoryUtil.memAddress(buffer), totalBytes, offset, completion); }
public VertexBuffer fill(List<IntObjectPair<byte[]>> data) { return fill(0, data); }
public abstract VertexBuffer fill(int offset, List<IntObjectPair<byte[]>> data);
@@ -0,0 +1,12 @@
package speiger.src.coreengine.graphics.api.compute;
public enum BarrierType {
MESH_BUFFER,
SHADER_TEXTURE,
SHADER_IMAGE,
SHADER_STORAGE,
SHADER_UNIFORM,
INDIRECT_COMMAND,
FRAMEBUFFER,
CPU;
}
@@ -0,0 +1,60 @@
package speiger.src.coreengine.graphics.api.compute;
import java.util.List;
import java.util.Objects;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.lists.ObjectList;
import speiger.src.coreengine.assets.api.ID;
import speiger.src.coreengine.graphics.api.shader.states.Bindings.BufferBinding;
import speiger.src.coreengine.graphics.api.shader.states.Bindings.BufferMode;
import speiger.src.coreengine.graphics.api.shader.states.Bindings.TextureBinding;
import speiger.src.coreengine.graphics.api.shader.states.Bindings.TextureMode;
import speiger.src.coreengine.graphics.api.texture.states.TextureType;
public record ComputePipeline(ID id, ID shader, List<BufferBinding> uniforms, List<TextureBinding> textures) {
public static Builder builder(ID id) {
return new Builder(id);
}
public static class Builder {
ID id;
ID shader;
ObjectList<BufferBinding> uniforms = new ObjectArrayList<>();
ObjectList<TextureBinding> textures = new ObjectArrayList<>();
private Builder(ID id) {
this.id = Objects.requireNonNull(id);
}
public Builder withShader(ID shader) {
this.shader = Objects.requireNonNull(shader);
return this;
}
public Builder withUniform(String name, int binding, int slot, int bytes) {
uniforms.add(new BufferBinding(Objects.requireNonNull(name), binding, slot, bytes, BufferMode.UNIFORM));
return this;
}
public Builder withBuffer(String name, int binding, int slot, int bytes) {
uniforms.add(new BufferBinding(Objects.requireNonNull(name), binding, slot, bytes, BufferMode.STORAGE));
return this;
}
public Builder withTexture(String name, int binding, int slot, TextureType type) {
textures.add(new TextureBinding(Objects.requireNonNull(name), binding, slot, type, TextureMode.SAMPLER));
return this;
}
public Builder withStorage(String name, int binding, int slot, TextureType type) {
textures.add(new TextureBinding(Objects.requireNonNull(name), binding, slot, type, TextureMode.IMAGE_BUFFER));
return this;
}
public ComputePipeline build() {
return new ComputePipeline(id, Objects.requireNonNull(shader, "A Shader has to exist"), uniforms, textures);
}
}
}
@@ -0,0 +1,5 @@
package speiger.src.coreengine.graphics.api.core;
public interface CommandBuffer {
int commandCount();
}
@@ -0,0 +1,30 @@
package speiger.src.coreengine.graphics.api.core;
import speiger.src.coreengine.graphics.api.buffer.VertexBuffer;
import speiger.src.coreengine.graphics.api.compute.BarrierType;
import speiger.src.coreengine.graphics.api.compute.ComputePipeline;
import speiger.src.coreengine.graphics.api.sampler.Sampler;
import speiger.src.coreengine.graphics.api.texture.Texture;
import speiger.src.coreengine.graphics.api.utils.AccessType;
import speiger.src.coreengine.graphics.api.utils.GraphicsResource;
public interface ComputeCommandBuffer extends GraphicsResource, CommandBuffer {
ComputeCommandBuffer begin();
ComputeCommandBuffer pipeline(ComputePipeline pipeline);
ComputeCommandBuffer texture(int binding, Texture texture, Sampler sampler);
ComputeCommandBuffer texture(int binding, Texture texture, AccessType access);
ComputeCommandBuffer uniform(int binding, VertexBuffer buffer);
ComputeCommandBuffer uniform(int binding, VertexBuffer buffer, long offset, long size);
ComputeCommandBuffer storage(int binding, VertexBuffer buffer);
ComputeCommandBuffer storage(int binding, VertexBuffer buffer, long offset, long size);
ComputeCommandBuffer dispatch(int numGroupsX, int numGroupsY, int numGroupsZ);
ComputeCommandBuffer barrier(BarrierType... types);
ComputeCommandBuffer end();
}
@@ -1,6 +1,6 @@
package speiger.src.coreengine.graphics.api.core;
import speiger.src.coreengine.rendering.input.window.Window;
import speiger.src.coreengine.input.window.Window;
public interface Graphics {
public String getName();
@@ -5,19 +5,24 @@ import speiger.src.coreengine.graphics.api.mesh.Mesh;
import speiger.src.coreengine.graphics.api.sampler.Sampler;
import speiger.src.coreengine.graphics.api.shader.ShaderPipeline;
import speiger.src.coreengine.graphics.api.texture.Texture;
import speiger.src.coreengine.graphics.api.utils.AccessType;
import speiger.src.coreengine.graphics.api.utils.GraphicsResource;
public interface GraphicsCommandBuffer extends GraphicsResource {
public interface GraphicsCommandBuffer extends GraphicsResource, CommandBuffer {
GraphicsCommandBuffer begin();
GraphicsCommandBuffer setScissors(int x, int y, int width, int height);
GraphicsCommandBuffer pipeline(ShaderPipeline pipeline);
GraphicsCommandBuffer mesh(Mesh mesh);
GraphicsCommandBuffer texture(int binding, Texture texture, Sampler sampler);
GraphicsCommandBuffer texture(int binding, Texture texture, AccessType access);
GraphicsCommandBuffer uniform(int binding, VertexBuffer buffer);
GraphicsCommandBuffer uniform(int binding, VertexBuffer buffer, long offset, long size);
GraphicsCommandBuffer storage(int binding, VertexBuffer buffer);
GraphicsCommandBuffer storage(int binding, VertexBuffer buffer, long offset, long size);
GraphicsCommandBuffer drawArrays(int offset, int count);
GraphicsCommandBuffer drawElements(int offset, int count);
@@ -25,5 +30,4 @@ public interface GraphicsCommandBuffer extends GraphicsResource {
GraphicsCommandBuffer popScissors();
GraphicsCommandBuffer end();
int commandCount();
}
@@ -22,10 +22,14 @@ public interface GraphicsCommandQueue {
public void clearTexture(Texture texture, int x, int y, int width, int height, int clearParam);
public void writeToTexture(Texture target, TextureFormat format, GraphicsDataType dataType, long source);
public default void writeToTexture(Texture target, int x, int y, int width, int height, TextureFormat format, GraphicsDataType dataType, long source) {
writeToTexture(target, 0, 0, x, y, width, height, format, dataType, source);
public default void writeToTexture(Texture target, int level, int x, int y, int width, int height, TextureFormat format, GraphicsDataType dataType, long source) {
writeToTexture(target, level, 0, 0, x, y, width, height, format, dataType, source);
}
public void writeToTexture(Texture target, int sourceX, int sourceY, int targetX, int targetY, int width, int height, TextureFormat format, GraphicsDataType dataType, long source);
public void writeToTexture(Texture target, int level, int sourceX, int sourceY, int targetX, int targetY, int width, int height, TextureFormat format, GraphicsDataType dataType, long source);
public void submitCommands(GraphicsCommandBuffer buffer);
public void submitCommands(CommandBuffer buffer);
public GraphicsFence createFence();
public long currentFrame();
public void submitFrame();
}
@@ -5,6 +5,7 @@ import java.util.List;
import speiger.src.coreengine.graphics.api.buffer.VertexBuffer;
import speiger.src.coreengine.graphics.api.buffer.states.BufferState;
import speiger.src.coreengine.graphics.api.buffer.states.BufferType;
import speiger.src.coreengine.graphics.api.compute.ComputePipeline;
import speiger.src.coreengine.graphics.api.mesh.Mesh;
import speiger.src.coreengine.graphics.api.sampler.Sampler;
import speiger.src.coreengine.graphics.api.sampler.SamplerSettings;
@@ -12,7 +13,7 @@ import speiger.src.coreengine.graphics.api.shader.ShaderPipeline;
import speiger.src.coreengine.graphics.api.texture.Texture;
import speiger.src.coreengine.graphics.api.texture.TextureSettings;
import speiger.src.coreengine.graphics.api.utils.ExecutionType;
import speiger.src.coreengine.rendering.input.window.Window;
import speiger.src.coreengine.input.window.Window;
public interface GraphicsDevice {
Window window();
@@ -24,5 +25,6 @@ public interface GraphicsDevice {
Texture createTexture(TextureSettings data, int width, int height);
Sampler createSampler(SamplerSettings settings);
Mesh createMesh(Mesh.Builder builder);
void preloadPipelines(List<ShaderPipeline> pipelines);
TimeQueryPool createQueryPool(int size);
void preloadPipelines(List<ShaderPipeline> graphics, List<ComputePipeline> compute);
}
@@ -0,0 +1,7 @@
package speiger.src.coreengine.graphics.api.core;
import speiger.src.coreengine.graphics.api.utils.GraphicsResource;
public interface GraphicsFence extends GraphicsResource {
boolean awaitCompletion(long timeout);
}
@@ -0,0 +1,11 @@
package speiger.src.coreengine.graphics.api.core;
import java.util.OptionalLong;
import speiger.src.coreengine.graphics.api.utils.GraphicsResource;
public interface TimeQueryPool extends GraphicsResource {
public int size();
public void write(int index);
public OptionalLong get(int index);
}
@@ -1,5 +0,0 @@
package speiger.src.coreengine.graphics.api.shader;
public interface CompiledPipeline {
}
@@ -6,23 +6,25 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import speiger.src.collections.objects.lists.ImmutableObjectList;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.lists.ObjectList;
import speiger.src.coreengine.assets.api.ID;
import speiger.src.coreengine.graphics.api.shader.states.Bindings.BufferMode;
import speiger.src.coreengine.graphics.api.shader.states.Bindings.TextureBinding;
import speiger.src.coreengine.graphics.api.shader.states.Bindings.UniformBinding;
import speiger.src.coreengine.graphics.api.shader.states.Bindings.TextureMode;
import speiger.src.coreengine.graphics.api.shader.states.Bindings.BufferBinding;
import speiger.src.coreengine.graphics.api.shader.states.DrawMode;
import speiger.src.coreengine.graphics.api.shader.states.GraphicsDataType;
import speiger.src.coreengine.graphics.api.shader.states.PolygonMode;
import speiger.src.coreengine.graphics.api.shader.states.ShaderType;
import speiger.src.coreengine.graphics.api.texture.states.TextureType;
import speiger.src.coreengine.graphics.api.vertex.VertexLayout;
public record ShaderPipeline(ID id, Map<ShaderType, ID> shaders, List<VertexBinding> attributes, List<UniformBinding> uniforms, List<TextureBinding> textures, RasterizerState rasterizer, ColorTarget colorTarget, DepthTarget depthTarget) {
public record ShaderPipeline(ID id, Map<ShaderType, ID> shaders, List<VertexBinding> attributes, List<BufferBinding> uniforms, List<TextureBinding> textures, RasterizerState rasterizer, ColorTarget colorTarget, DepthTarget depthTarget) {
public ShaderPipeline {
Objects.requireNonNull(id);
if(shaders.isEmpty()) throw new IllegalStateException("Shaders isn't allowed to be empty");
if(shaders.containsKey(ShaderType.COMPUTE)) throw new IllegalStateException("Graphics Pipelines are not allowed to have Compute Shaders");
Objects.requireNonNull(uniforms);
Objects.requireNonNull(textures);
Objects.requireNonNull(rasterizer);
@@ -34,34 +36,18 @@ public record ShaderPipeline(ID id, Map<ShaderType, ID> shaders, List<VertexBind
return new Builder(id);
}
public Builder copy(ID id) {
return new Builder(id, this);
}
public static class Builder {
ID id;
EnumMap<ShaderType, ID> shaders = new EnumMap<>(ShaderType.class);
ObjectList<VertexBinding> attributes = new ObjectArrayList<>();
ObjectList<UniformBinding> uniforms = new ObjectArrayList<>();
ObjectList<BufferBinding> uniforms = new ObjectArrayList<>();
ObjectList<TextureBinding> textures = new ObjectArrayList<>();
RasterizerState rasterizer = RasterizerState.DEFAULT;
ColorTarget colorTarget = ColorTarget.DEFAULT;
DepthTarget depthTarget = DepthTarget.DEFAULT;
private Builder(ID id) {
this.id = Objects.requireNonNull(id);
}
private Builder(ID id, ShaderPipeline line) {
this(id);
shaders.putAll(line.shaders());
attributes.addAll(line.attributes());
uniforms.addAll(line.uniforms());
textures.addAll(line.textures());
rasterizer = line.rasterizer();
colorTarget = line.colorTarget();
depthTarget = line.depthTarget();
this.id = Objects.requireNonNull(id);
}
public Builder withStage(ShaderType type, ID location) {
@@ -70,38 +56,49 @@ public record ShaderPipeline(ID id, Map<ShaderType, ID> shaders, List<VertexBind
}
public Builder withUniform(String name, int binding, int slot, int bytes) {
uniforms.add(new UniformBinding(name, binding, slot, bytes));
uniforms.add(new BufferBinding(Objects.requireNonNull(name), binding, slot, bytes, BufferMode.UNIFORM));
return this;
}
public Builder withBuffer(String name, int binding, int slot, int bytes) {
uniforms.add(new BufferBinding(Objects.requireNonNull(name), binding, slot, bytes, BufferMode.STORAGE));
return this;
}
public Builder withTexture(String name, int binding, int slot, TextureType type) {
textures.add(new TextureBinding(name, binding, slot, type));
textures.add(new TextureBinding(Objects.requireNonNull(name), binding, slot, type, TextureMode.SAMPLER));
return this;
}
public Builder withStorage(String name, int binding, int slot, TextureType type) {
textures.add(new TextureBinding(Objects.requireNonNull(name), binding, slot, type, TextureMode.IMAGE_BUFFER));
return this;
}
public Builder withRasterizer(DrawMode draw, PolygonMode polygon, boolean cullBack) {
rasterizer = new RasterizerState(draw, polygon, cullBack);
rasterizer = new RasterizerState(Objects.requireNonNull(draw), Objects.requireNonNull(polygon), cullBack);
return this;
}
public AttributeBuilder withFormat() {
return withFormat(attributes.size());
public Builder withFormat(VertexLayout layout) {
return withFormat(attributes.size(), layout);
}
public AttributeBuilder withInstanceFormat() {
return withFormat(attributes.size(), 1);
public Builder withInstanceFormat(VertexLayout layout) {
return withFormat(attributes.size(), layout, 1);
}
public AttributeBuilder withInstanceFormat(int instanceCount) {
return withFormat(attributes.size(), instanceCount);
public Builder withInstanceFormat(VertexLayout layout, int instanceCount) {
return withFormat(attributes.size(), layout, instanceCount);
}
public AttributeBuilder withFormat(int bindingIndex) {
return withFormat(bindingIndex, 0);
public Builder withFormat(int bindingIndex, VertexLayout layout) {
return withFormat(bindingIndex, layout, 0);
}
public AttributeBuilder withFormat(int bindingIndex, int instanceCount) {
return new AttributeBuilder(this, bindingIndex, instanceCount);
public Builder withFormat(int bindingIndex, VertexLayout layout, int instanceCount) {
attributes.add(new VertexBinding(bindingIndex, Objects.requireNonNull(layout), instanceCount));
return this;
}
public Builder withColorTarget(ColorTarget target) {
@@ -116,52 +113,8 @@ public record ShaderPipeline(ID id, Map<ShaderType, ID> shaders, List<VertexBind
public ShaderPipeline build() {
if(shaders.isEmpty()) throw new IllegalStateException("Shaders must be provided in a Shader Pipeline");
if(shaders.containsKey(ShaderType.COMPUTE)) throw new IllegalStateException("Graphics Pipelines are not allowed to have Compute Shaders");
return new ShaderPipeline(id, Collections.unmodifiableMap(shaders), attributes.unmodifiable(), uniforms.unmodifiable(), textures.unmodifiable(), Objects.requireNonNull(rasterizer, "A Rasterizer should be defined"), colorTarget, depthTarget);
}
}
public static class AttributeBuilder {
final Builder owner;
final List<BufferAttribute> attributes = new ObjectArrayList<>();
final int bindingIndex;
final int instanceCount;
int stride;
private AttributeBuilder(Builder owner, int bindingIndex, int instanceCount) {
this.owner = owner;
this.bindingIndex = bindingIndex;
this.instanceCount = instanceCount;
}
public AttributeBuilder attribute(String name, int index, int size) {
return attribute(name, index, size, GraphicsDataType.FLOAT, false);
}
public AttributeBuilder attribute(String name, int index, int size, GraphicsDataType type) {
return attribute(name, index, size, type, false);
}
public AttributeBuilder attribute(String name, int index, int size, GraphicsDataType type, boolean normalized) {
attributes.add(new BufferAttribute(name, index, size, type, normalized));
stride += type.size(size);
return this;
}
public AttributeBuilder array(String baseName, int startIndex, int width, int size, GraphicsDataType type) {
return array(baseName, startIndex, width, size, type, false);
}
public AttributeBuilder array(String baseName, int startIndex, int width, int size, GraphicsDataType type, boolean normalized) {
Objects.requireNonNull(baseName);
for(int i = 0;i<width;i++) {
attribute(baseName+" ["+i+"]", startIndex + i, size, type, normalized);
}
return this;
}
public Builder endFormat() {
owner.attributes.add(new VertexBinding(bindingIndex, stride, instanceCount, new ImmutableObjectList<>(attributes)));
return owner;
}
}
}
@@ -1,7 +1,11 @@
package speiger.src.coreengine.graphics.api.shader;
import java.util.List;
import speiger.src.collections.objects.collections.ObjectIterable;
import speiger.src.coreengine.graphics.api.vertex.VertexLayout;
import speiger.src.coreengine.graphics.api.vertex.VertexLayout.Element;
public record VertexBinding(int bindingIndex, int stide, int instanceOffset, List<BufferAttribute> attributes) {
public record VertexBinding(int bindingIndex, VertexLayout layout, int instanceOffset) {
public ObjectIterable<Element> elements() {
return layout.elements();
}
}
@@ -5,17 +5,28 @@ import java.util.Objects;
import speiger.src.coreengine.graphics.api.texture.states.TextureType;
public class Bindings {
public record UniformBinding(String name, int binding, int slot, int bytes) {
public UniformBinding {
public record BufferBinding(String name, int binding, int slot, int bytes, BufferMode mode) {
public BufferBinding {
Objects.requireNonNull(name, "A name has to be provided");
Objects.requireNonNull(mode, "A Mode has to be provided");
}
}
public record TextureBinding(String name, int binding, int slot, TextureType type) {
public record TextureBinding(String name, int binding, int slot, TextureType type, TextureMode mode) {
public TextureBinding {
Objects.requireNonNull(name, "A name has to be provided");
Objects.requireNonNull(type, "A TextureType has to be provided");
Objects.requireNonNull(mode, "A Mode has to be provided");
}
}
public static enum BufferMode {
UNIFORM,
STORAGE;
}
public static enum TextureMode {
SAMPLER,
IMAGE_BUFFER;
}
}
@@ -5,5 +5,6 @@ public enum ShaderType {
FRAGMENT,
GEOMETRY,
TESSELATION_CONTROL,
TESSELATION_EVALUATION;
TESSELATION_EVALUATION,
COMPUTE;
}
@@ -0,0 +1,201 @@
package speiger.src.coreengine.graphics.api.texture.drawable;
import java.nio.ByteBuffer;
import java.util.Objects;
import org.lwjgl.stb.STBImage;
import org.lwjgl.stb.STBTTFontinfo;
import org.lwjgl.stb.STBTruetype;
import org.lwjgl.system.MemoryUtil;
import org.lwjgl.util.freetype.FT_Bitmap;
import org.lwjgl.util.freetype.FT_Face;
import org.lwjgl.util.freetype.FT_GlyphSlot;
import org.lwjgl.util.freetype.FreeType;
import speiger.src.coreengine.graphics.api.core.GraphicsCommandQueue;
import speiger.src.coreengine.graphics.api.shader.states.GraphicsDataType;
import speiger.src.coreengine.graphics.api.texture.Texture;
import speiger.src.coreengine.graphics.api.texture.states.TextureFormat;
import speiger.src.coreengine.math.color.ColorSpaces;
public class Drawable implements IDrawable, AutoCloseable {
TextureFormat format;
int width;
int height;
long components;
long pixels;
boolean isSTB;
public Drawable(TextureFormat format, int width, int height) {
this(format, width, height, MemoryUtil.nmemAllocChecked(format.components() * width * height), false);
}
public Drawable(TextureFormat format, int width, int height, long pixels, boolean isSTB) {
this.format = format;
this.width = width;
this.height = height;
this.pixels = pixels;
this.components = format.components();
this.isSTB = isSTB;
}
@Override
public ColorSpaces colorspace() { return ColorSpaces.ABGR; }
@Override
public int width() { return width; }
@Override
public int height() { return height; }
public long pixels() { return pixels; }
public boolean isSTBImage() { return isSTB; }
public TextureFormat format() { return format; }
@Override
public void close() {
if(pixels == 0L) return;
if(isSTB) STBImage.nstbi_image_free(pixels);
else MemoryUtil.nmemFree(pixels);
pixels = 0L;
}
protected long offset(int x, int y) {
return ((y * width()) + x) * components;
}
protected void ensureValid(int index) {
ensureValid(index % width, index / width);
}
protected void ensureValid(int x, int y) {
if(x < 0 || y < 0) throw new ArrayIndexOutOfBoundsException("Index out of bounds: X=["+x+"], Y=["+y+"]");
if(x > width || y > height) throw new ArrayIndexOutOfBoundsException("Index out of bounds: X=["+x+"], Y=["+y+"], width=["+width+"], height=["+height+"]");
if(pixels == 0L) throw new IllegalStateException("Pixel Data doesn't exist");
}
public boolean drawFont(FT_Face face, int glyth) {
ensureValid(0);
if(components != 1) throw new IllegalStateException("Format has to be 1 component");
if(FreeType.FT_Load_Glyph(face, glyth, 4) != 0) return false;
FT_GlyphSlot slot = Objects.requireNonNull(face.glyph());
FT_Bitmap map = slot.bitmap();
if(map.pixel_mode() != FreeType.FT_PIXEL_MODE_GRAY) throw new IllegalStateException("Pixel isn't a grayscale picture");
if(map.width() != width() || map.rows() != height()) throw new IllegalStateException("Bounds do not match");
int size = map.width() * map.rows();
ByteBuffer buffer = Objects.requireNonNull(map.buffer(size));
MemoryUtil.memCopy(MemoryUtil.memAddress(buffer), pixels(), size);
return true;
}
public void drawFont(STBTTFontinfo info, int glyth, float sourceX, float sourceY, int targetX, int targetY, int width, int height, float scaleX, float scaleY) {
ensureValid(targetX, targetY);
ensureValid(targetX + width, targetY + height);
if(components != 1) throw new IllegalStateException("Format has to be 1 component");
STBTruetype.nstbtt_MakeGlyphBitmapSubpixel(info.address(), pixels + offset(targetX, targetY), width, height, width(), scaleX, scaleY, sourceX, sourceY, glyth);
}
public void upload(GraphicsCommandQueue queue, Texture texture) {
if(texture.width() != width || texture.height() != height) throw new IllegalStateException("Texture[w="+texture.width()+",h="+texture.height()+"] bounds does not match the drawable [w="+width+",h="+height+"]");
queue.writeToTexture(texture, format, GraphicsDataType.UNSIGNED_BYTE, pixels);
}
public void upload(GraphicsCommandQueue queue, Texture texture, int targetX, int targetY, int sourceX, int sourceY, int width, int height) {
upload(queue, texture, 0, targetX, targetY, sourceX, sourceY, width, height);
}
public void upload(GraphicsCommandQueue queue, Texture texture, int level, int targetX, int targetY, int sourceX, int sourceY, int width, int height) {
ensureValid(sourceX, sourceY);
ensureValid(sourceX + width, sourceY + height);
queue.writeToTexture(texture, level, sourceX, sourceY, targetX, targetY, width, height, format, GraphicsDataType.UNSIGNED_BYTE, pixels);
}
@Override
public void set(int index, int data) {
ensureValid(index);
if(components != 4) throw new IllegalStateException("Format has to be 4 components");
MemoryUtil.memPutInt(this.pixels + index * 4L, data);
}
@Override
public void setR(int index, int red) {
if(!format.hasRed()) throw new IllegalArgumentException("Format doesn't support Red/Luminance");
ensureValid(index);
MemoryUtil.memPutByte(this.pixels + index * components + format.redOffset(), (byte)(red & 0xFF));
}
@Override
public void setG(int index, int green) {
if(!format.hasGreen()) throw new IllegalArgumentException("Format doesn't support Green");
ensureValid(index);
MemoryUtil.memPutByte(this.pixels + index * components + format.greenOffset(), (byte)(green & 0xFF));
}
@Override
public void setB(int index, int blue) {
if(!format.hasBlue()) throw new IllegalArgumentException("Format doesn't support Blue");
ensureValid(index);
MemoryUtil.memPutByte(this.pixels + index * components + format.blueOffset(), (byte)(blue & 0xFF));
}
@Override
public void setA(int index, int alpha) {
if(!format.hasAlpha()) throw new IllegalArgumentException("Format doesn't support Alpha");
ensureValid(index);
MemoryUtil.memPutByte(this.pixels + index * components + format.alphaOffset(), (byte)(alpha & 0xFF));
}
@Override
public void fill(int x, int y, int width, int height, int data) {
if(components != 4 && components != 1) throw new IllegalStateException("Format has to be 1 or 4 components");
ensureValid(x, y);
ensureValid(x+width, y+height);
if(components == 1) {
for(int xOff = 0;xOff<width;xOff++) {
for(int yOff = 0;yOff<height;yOff++) {
MemoryUtil.memPutByte(this.pixels + offset(x+xOff, y+yOff), (byte)(data & 0xFF));
}
}
return;
}
for(int xOff = 0;xOff<width;xOff++) {
for(int yOff = 0;yOff<height;yOff++) {
MemoryUtil.memPutInt(this.pixels + offset(x+xOff, y+yOff), data);
}
}
}
@Override
public int get(int index) {
if(components != 4) throw new IllegalStateException("Format has to be 4 components");
ensureValid(index);
return MemoryUtil.memGetInt(this.pixels + index * 4L);
}
@Override
public int getR(int index) {
if(!format.hasRed()) throw new IllegalArgumentException("Format doesn't support Red/Luminance");
ensureValid(index);
return MemoryUtil.memGetByte(this.pixels + index * components + format.redOffset());
}
@Override
public int getG(int index) {
if(!format.hasGreen()) throw new IllegalArgumentException("Format doesn't support Green");
ensureValid(index);
return MemoryUtil.memGetByte(this.pixels + index * components + format.greenOffset());
}
@Override
public int getB(int index) {
if(!format.hasBlue()) throw new IllegalArgumentException("Format doesn't support Blue");
ensureValid(index);
return MemoryUtil.memGetByte(this.pixels + index * components + format.blueOffset());
}
@Override
public int getA(int index) {
if(!format.hasAlpha()) throw new IllegalArgumentException("Format doesn't support Alpha");
ensureValid(index);
return MemoryUtil.memGetByte(this.pixels + index * components + format.alphaOffset());
}
}
@@ -0,0 +1,43 @@
package speiger.src.coreengine.graphics.api.texture.drawable;
import speiger.src.coreengine.math.color.ColorSpaces;
public interface IDrawable {
public ColorSpaces colorspace();
public int width();
public int height();
public void set(int index, int data);
public default void set(int x, int y, int data) { set((y * width()) + x, data); }
public default void set(int index, int red, int green, int blue, int alpha) { set(index, colorspace().color(red, green, blue, alpha)); }
public default void set(int x, int y, int red, int green, int blue, int alpha) { set((y * width()) + x, colorspace().color(red, green, blue, alpha)); }
public void setR(int index, int red);
public default void setR(int x, int y, int red) { setR((y * width()) + x, red); }
public void setG(int index, int green);
public default void setG(int x, int y, int green) { setG((y * width()) + x, green); }
public void setB(int index, int blue);
public default void setB(int x, int y, int blue) { setB((y * width()) + x, blue); }
public void setA(int index, int alpha);
public default void setA(int x, int y, int alpha) { setA((y * width()) + x, alpha); }
public void fill(int x, int y, int width, int height, int data);
public default void fill(int x, int y, int width, int height, int red, int green, int blue, int alpha) { fill(x, y, width, height, colorspace().color(red, green, blue, alpha)); }
public int get(int index);
public default int get(int x, int y) { return get((y * width()) + x); }
public int getR(int index);
public default int getR(int x, int y) { return getR((y * width()) + x); }
public int getG(int index);
public default int getG(int x, int y) { return getG((y * width()) + x); }
public int getB(int index);
public default int getB(int x, int y) { return getB((y * width()) + x); }
public int getA(int index);
public default int getA(int x, int y) { return getA((y * width()) + x); }
}
@@ -1,22 +1,40 @@
package speiger.src.coreengine.graphics.api.texture.states;
public enum TextureFormat {
R(1),
RG(2),
RGB(3),
RGBA(4),
DEPTH(1),
DEPTH_STENCIL(2),
LUMINANCE(1),
LUMINANCE_ALPHA(2);
R(1, 0, -1, -1, -1),
RG(2, 0, 1, -1, -1),
RGB(3, 0, 1, 2, -1),
RGBA(4, 0, 1, 2, 3),
DEPTH(1, 0, -1, -1, -1),
DEPTH_STENCIL(2, 0, 1, -1, -1),
LUMINANCE(1, 0, 0, 0, 0),
LUMINANCE_ALPHA(2, 0, -1, -1, 1);
int components;
private TextureFormat(int components) {
int componentMask;
int redOffset;
int greenOffset;
int blueOffset;
int alphaOffset;
private TextureFormat(int components, int redOffset, int greenOffset, int blueOffset, int alphaOffset) {
this.components = components;
this.componentMask = (1 << (components * 8)) - 1;
this.redOffset = redOffset;
this.greenOffset = greenOffset;
this.blueOffset = blueOffset;
this.alphaOffset = alphaOffset;
}
public int components() {
return components;
}
public int components() { return components; }
public int componentMask() { return componentMask; }
public int redOffset() { return redOffset; }
public int greenOffset() { return greenOffset; }
public int blueOffset() { return blueOffset; }
public int alphaOffset() { return alphaOffset; }
public boolean hasRed() { return redOffset != -1; }
public boolean hasGreen() { return greenOffset != -1; }
public boolean hasBlue() { return blueOffset != -1; }
public boolean hasAlpha() { return alphaOffset != -1; }
}
@@ -0,0 +1,7 @@
package speiger.src.coreengine.graphics.api.utils;
public enum AccessType {
READ,
WRITE,
BOTH;
}
@@ -0,0 +1,6 @@
package speiger.src.coreengine.graphics.api.utils;
public enum BufferOwner {
KEPT,
GIVEN;
}
@@ -2,7 +2,9 @@ package speiger.src.coreengine.graphics.opengl.buffer;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Objects;
import org.jspecify.annotations.NonNull;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL45;
import org.lwjgl.system.MemoryUtil;
@@ -11,6 +13,7 @@ import speiger.src.collections.ints.misc.pairs.IntObjectPair;
import speiger.src.coreengine.graphics.api.buffer.VertexBuffer;
import speiger.src.coreengine.graphics.api.buffer.states.BufferState;
import speiger.src.coreengine.graphics.api.buffer.states.BufferType;
import speiger.src.coreengine.graphics.api.utils.BufferOwner;
import speiger.src.coreengine.graphics.opengl.utils.GLUtils;
import speiger.src.coreengine.rendering.utils.AllocationTracker;
import speiger.src.coreengine.utils.io.GameLog;
@@ -65,21 +68,23 @@ public class GLVertexBuffer extends VertexBuffer {
}
@Override
public GLVertexBuffer set(long pointer, int totalBytes) {
public GLVertexBuffer set(long pointer, int totalBytes, BufferOwner ownership) {
this.size = totalBytes;
GL45.nglNamedBufferData(id, totalBytes, pointer, GLUtils.toGL(usage));
return this;
}
@Override
public GLVertexBuffer fill(long pointer, int totalBytes, int offset) {
public GLVertexBuffer fill(long pointer, int totalBytes, int offset, BufferOwner ownerhip) {
GL45.nglNamedBufferSubData(id, offset, totalBytes, pointer);
return this;
}
@Override
public GLVertexBuffer read(long pointer, int totalBytes, int offset) {
public GLVertexBuffer read(long pointer, int totalBytes, int offset, @NonNull Runnable completion) {
Objects.requireNonNull(completion, "Completion has to be required otherwise you can't see when the data is there");
GL45.nglGetNamedBufferSubData(id, offset, totalBytes, pointer);
completion.run();
return this;
}
@@ -107,10 +112,7 @@ public class GLVertexBuffer extends VertexBuffer {
MemoryUtil.memCopy(MemoryUtil.memAddress(buffer), MemoryUtil.memAddress(newBuff), newSize);
newBuff.position(newSize).flip();
GL45.glUnmapNamedBuffer(id);
set(MemoryUtil.memAddress(newBuff), newSize);
MemoryUtil.memFree(newBuff);
return this;
return set(MemoryUtil.memAddress(newBuff), newSize, BufferOwner.GIVEN);
}
@Override
@@ -122,10 +124,7 @@ public class GLVertexBuffer extends VertexBuffer {
MemoryUtil.memCopy(MemoryUtil.memAddress(buffer), MemoryUtil.memAddress(newBuff), size);
newBuff.position(newSize).flip();
GL45.glUnmapNamedBuffer(id);
set(MemoryUtil.memAddress(newBuff), newSize);
MemoryUtil.memFree(newBuff);
return this;
return set(MemoryUtil.memAddress(newBuff), newSize, BufferOwner.GIVEN);
}
@Override
@@ -1,14 +1,17 @@
package speiger.src.coreengine.graphics.opengl.core;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL32;
import org.lwjgl.opengl.GL45;
import speiger.src.coreengine.assets.api.ID;
import speiger.src.coreengine.graphics.api.core.GraphicsCommandBuffer;
import speiger.src.coreengine.graphics.api.core.CommandBuffer;
import speiger.src.coreengine.graphics.api.core.GraphicsCommandQueue;
import speiger.src.coreengine.graphics.api.shader.states.GraphicsDataType;
import speiger.src.coreengine.graphics.api.target.RenderTarget;
@@ -23,9 +26,9 @@ import speiger.src.coreengine.graphics.opengl.utils.GLStates;
import speiger.src.coreengine.graphics.opengl.utils.GLUtils;
import speiger.src.coreengine.graphics.opengl.utils.ViewPortStack;
import speiger.src.coreengine.graphics.opengl.utils.states.ScissorState;
import speiger.src.coreengine.input.window.Window;
import speiger.src.coreengine.math.vector.floats.Vec4f;
import speiger.src.coreengine.math.vector.ints.Vec4i;
import speiger.src.coreengine.rendering.input.window.Window;
public class GLCommandQueue implements GraphicsCommandQueue {
GLGraphicsDevice device;
@@ -33,6 +36,8 @@ public class GLCommandQueue implements GraphicsCommandQueue {
RenderTarget renderTarget = ScreenTarget.INSTANCE;
ScreenBuffer targetFBO;
int tempWriteFBO;
Deque<Frame> frames = new ArrayDeque<>(4);
long currentFrame = 0L;
Vec4f clearColor = Vec4f.mutable(0F, 0F, 0F, 1F);
double clearDepth = 0D;
@@ -40,6 +45,8 @@ public class GLCommandQueue implements GraphicsCommandQueue {
this.device = device;
this.targetFBO = new ScreenBuffer(0, device.window().width(), device.window().height());
tempWriteFBO = GL45.glCreateFramebuffers();
frames.add(new Frame(-2));
frames.add(new Frame(-1));
}
@Override
@@ -67,10 +74,33 @@ public class GLCommandQueue implements GraphicsCommandQueue {
}
@Override
public void submitCommands(GraphicsCommandBuffer buffer) {
public void submitCommands(CommandBuffer buffer) {
if(buffer instanceof GLRecordingCommandBuffer recorder) {
recorder.execute();
}
else if(buffer instanceof GLRecordingComputeCommandBuffer recorder) {
recorder.execute();
}
}
@Override
public long currentFrame() {
return currentFrame;
}
@Override
public void submitFrame() {
frames.getFirst().fenceIn();
currentFrame++;
frames.add(new Frame(currentFrame));
if(!frames.pop().isCompleted(Long.MAX_VALUE)) {
throw new IllegalStateException("Couldn't complete frames");
}
}
@Override
public GLFence createFence() {
return new GLFence(frames.getFirst());
}
private Vec4f setClearColor(Vec4f value) {
@@ -159,13 +189,13 @@ public class GLCommandQueue implements GraphicsCommandQueue {
}
@Override
public void writeToTexture(Texture target, int sourceX, int sourceY, int targetX, int targetY, int width, int height, TextureFormat format, GraphicsDataType dataType, long source) {
public void writeToTexture(Texture target, int level, int sourceX, int sourceY, int targetX, int targetY, int width, int height, TextureFormat format, GraphicsDataType dataType, long source) {
GLStates states = device.states;
states.unpack_alignment.set(target.settings().internal().components());
states.unpack_row_length.set(target.width());
states.unpack_skip_pixel.set(sourceX);
states.unpack_skip_rows.set(sourceY);
GL45.glTextureSubImage2D(((GLTexture)target).id(), 0, targetX, targetY, width, height, GLUtils.toGLExternal(format), GLUtils.toGL(dataType), source);
GL45.glTextureSubImage2D(((GLTexture)target).id(), level, targetX, targetY, width, height, GLUtils.toGLExternal(format), GLUtils.toGL(dataType), source);
states.unpack_alignment.setDefault();
states.unpack_row_length.setDefault();
states.unpack_skip_pixel.setDefault();
@@ -182,4 +212,33 @@ public class GLCommandQueue implements GraphicsCommandQueue {
}
private record ScreenBuffer(int fbo, int width, int height) {}
public class Frame {
final long frame;
boolean hasBegun;
long fenceId;
public Frame(long frame) {
this.frame = frame;
hasBegun = frame < 0;
}
public void fenceIn() {
hasBegun = true;
fenceId = GL32.glFenceSync(GL32.GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
}
public boolean isCompleted(long timeout) {
if(!hasBegun) {
if(timeout == 0) return false;
throw new IllegalStateException("Can't wait on something you are supposed to submit to");
}
if(fenceId == 0) return true;
int result = GL32.glClientWaitSync(fenceId, GL32.GL_SYNC_FLUSH_COMMANDS_BIT, timeout);
if(result == GL32.GL_TIMEOUT_EXPIRED) return false;
if(result == GL32.GL_WAIT_FAILED) throw new IllegalStateException("Waiting has failed?");
GL32.glDeleteSync(fenceId);
fenceId = 0L;
return true;
}
}
}
@@ -0,0 +1,29 @@
package speiger.src.coreengine.graphics.opengl.core;
import speiger.src.coreengine.graphics.api.core.GraphicsFence;
import speiger.src.coreengine.graphics.opengl.core.GLCommandQueue.Frame;
public class GLFence implements GraphicsFence {
boolean removed = false;
Frame frame;
public GLFence(Frame frame) {
this.frame = frame;
}
@Override
public boolean isRemoved() {
return removed;
}
@Override
public void remove() {
removed = true;
}
@Override
public boolean awaitCompletion(long timeout) {
return removed || frame.isCompleted(timeout);
}
}
@@ -5,7 +5,7 @@ import org.lwjgl.opengl.GL;
import speiger.src.coreengine.assets.manager.AssetManager;
import speiger.src.coreengine.graphics.api.core.Graphics;
import speiger.src.coreengine.rendering.input.window.Window;
import speiger.src.coreengine.input.window.Window;
public class GLGraphics implements Graphics {
AssetManager manager;
@@ -8,6 +8,7 @@ import java.util.Objects;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.function.IntPredicate;
import java.util.function.Supplier;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL12;
@@ -39,34 +40,38 @@ import speiger.src.coreengine.assets.manager.AssetManager;
import speiger.src.coreengine.graphics.api.buffer.VertexBuffer;
import speiger.src.coreengine.graphics.api.buffer.states.BufferState;
import speiger.src.coreengine.graphics.api.buffer.states.BufferType;
import speiger.src.coreengine.graphics.api.compute.ComputePipeline;
import speiger.src.coreengine.graphics.api.core.GraphicsCommandBuffer;
import speiger.src.coreengine.graphics.api.core.GraphicsDevice;
import speiger.src.coreengine.graphics.api.core.TimeQueryPool;
import speiger.src.coreengine.graphics.api.mesh.Mesh;
import speiger.src.coreengine.graphics.api.mesh.Mesh.LayoutInfo;
import speiger.src.coreengine.graphics.api.sampler.SamplerSettings;
import speiger.src.coreengine.graphics.api.shader.BufferAttribute;
import speiger.src.coreengine.graphics.api.shader.ShaderPipeline;
import speiger.src.coreengine.graphics.api.shader.VertexBinding;
import speiger.src.coreengine.graphics.api.shader.states.Bindings.BufferBinding;
import speiger.src.coreengine.graphics.api.shader.states.Bindings.BufferMode;
import speiger.src.coreengine.graphics.api.shader.states.Bindings.TextureBinding;
import speiger.src.coreengine.graphics.api.shader.states.Bindings.UniformBinding;
import speiger.src.coreengine.graphics.api.shader.states.Bindings.TextureMode;
import speiger.src.coreengine.graphics.api.shader.states.ShaderType;
import speiger.src.coreengine.graphics.api.texture.TextureSettings;
import speiger.src.coreengine.graphics.api.texture.states.SwizzleMask;
import speiger.src.coreengine.graphics.api.texture.states.TextureType;
import speiger.src.coreengine.graphics.api.utils.ExecutionType;
import speiger.src.coreengine.graphics.api.vertex.VertexLayout.Element;
import speiger.src.coreengine.graphics.opengl.buffer.GLVertexBuffer;
import speiger.src.coreengine.graphics.opengl.mesh.GLMesh;
import speiger.src.coreengine.graphics.opengl.sampler.GLSampler;
import speiger.src.coreengine.graphics.opengl.shader.ShaderCache;
import speiger.src.coreengine.graphics.opengl.shader.ShaderInstance;
import speiger.src.coreengine.graphics.opengl.shader.ShaderInstance.SamplerObject;
import speiger.src.coreengine.graphics.opengl.shader.ShaderInstance.BufferObject;
import speiger.src.coreengine.graphics.opengl.shader.ShaderInstance.ShaderStorage;
import speiger.src.coreengine.graphics.opengl.shader.ShaderInstance.UniformObject;
import speiger.src.coreengine.graphics.opengl.shader.ShaderInstance.TextureObject;
import speiger.src.coreengine.graphics.opengl.texture.GLTexture;
import speiger.src.coreengine.graphics.opengl.utils.GLFunctions;
import speiger.src.coreengine.graphics.opengl.utils.GLStates;
import speiger.src.coreengine.graphics.opengl.utils.GLUtils;
import speiger.src.coreengine.rendering.input.window.Window;
import speiger.src.coreengine.input.window.Window;
public class GLGraphicsDevice implements GraphicsDevice {
static final Map<ID, String> IMPORTS = Object2ObjectMap.builder().<ID, String>map().synchronize();
@@ -93,7 +98,7 @@ public class GLGraphicsDevice implements GraphicsDevice {
@Override
public GLSurface createSurface() {
return new GLSurface(owner);
return new GLSurface(owner, this);
}
@Override
@@ -161,10 +166,18 @@ public class GLGraphicsDevice implements GraphicsDevice {
}
@Override
public void preloadPipelines(List<ShaderPipeline> pipelines) {
public TimeQueryPool createQueryPool(int size) {
return new GLTimeQueryPool(size);
}
@Override
public void preloadPipelines(List<ShaderPipeline> graphics, List<ComputePipeline> compute) {
try(IAssetProvider provider = manager.get()) {
for(ShaderPipeline pipeline : pipelines) {
computeShaderProgram(pipeline, provider);
for(ShaderPipeline pipeline : graphics) {
programCache.put(pipeline.id(), computeShaderProgram(pipeline, provider));
}
for(ComputePipeline pipeline : compute) {
programCache.put(pipeline.id(), computeShaderProgram(pipeline, provider));
}
}
catch(Exception e) {
@@ -186,13 +199,52 @@ public class GLGraphicsDevice implements GraphicsDevice {
}
}
public ShaderInstance getShader(ComputePipeline pipeline) {
return programCache.supplyIfAbsent(pipeline.id(), () -> computeShaderProgram(pipeline));
}
private ShaderInstance computeShaderProgram(ComputePipeline pipeline) {
try(IAssetProvider provider = manager.get()) {
return computeShaderProgram(pipeline, provider);
}
catch(Exception e) {
e.printStackTrace();
return null;
}
}
private ShaderInstance computeShaderProgram(ComputePipeline pipeline, IAssetProvider provider) {
MultiAsset assets = MultiAsset.combineNonNull(provider, pipeline.shader());
if(assets == null) {
//TODO log stuff
return null;
}
int program = loadOrCreateProgram(pipeline.id(), assets::crc, T -> {
int id = shaderCache.computeIfAbsent(pipeline.shader(), E -> computeShader(E, assets.get(0), ShaderType.COMPUTE, provider));
if(id == -1) {
System.out.println("Couldn't create a compute shader Id["+pipeline.shader()+"]");
//TODO implement logging.
//Failed to compile shader
return false;
}
GL20.glAttachShader(T, id);
GL20.glLinkProgram(T);
GL20.glDetachShader(T, id);
return true;
});
if(program == 0) return null;
return generateShaderInstance(program, pipeline::textures, pipeline::uniforms);
}
private ShaderInstance computeShaderProgram(ShaderPipeline pipeline, IAssetProvider provider) {
MultiAsset assets = MultiAsset.combineNonNull(provider, pipeline.shaders().values().toArray(ID[]::new));
if(assets == null) {
//TODO log stuff
return null;
}
int program = loadOrCreateProgram(pipeline, assets, T -> {
int program = loadOrCreateProgram(pipeline.id(), assets::crc, T -> {
Map<ID, IAsset> map = Object2ObjectMap.builder().map();
assets.forEach(map, (K, V) -> K.put(V.location(), V));
IntList shaders = new IntArrayList();
@@ -209,8 +261,8 @@ public class GLGraphicsDevice implements GraphicsDevice {
GL20.glAttachShader(T, id);
shaders.add(id);
}
for(BufferAttribute attribute : ObjectIterables.flatMap(pipeline.attributes(), VertexBinding::attributes)) {
GL20.glBindAttribLocation(T, attribute.index(), attribute.name());
for(Element element : ObjectIterables.flatMap(pipeline.attributes(), VertexBinding::elements)) {
GL20.glBindAttribLocation(T, element.index(), element.name());
}
GL20.glLinkProgram(T);
for(int i = 0,m=shaders.size();i<m;i++) {
@@ -219,31 +271,34 @@ public class GLGraphicsDevice implements GraphicsDevice {
return true;
});
if(program == 0) return null;
ShaderStorage<UniformObject> uniforms = new ShaderStorage<>();
ShaderStorage<SamplerObject> samplers = new ShaderStorage<>();
for(TextureBinding binding : pipeline.textures()) {
return generateShaderInstance(program, pipeline::textures, pipeline::uniforms);
}
private ShaderInstance generateShaderInstance(int program, Supplier<List<TextureBinding>> textureProvider, Supplier<List<BufferBinding>> uniformProvider) {
ShaderStorage<BufferObject> uniforms = new ShaderStorage<>();
ShaderStorage<BufferObject> storages = new ShaderStorage<>();
ShaderStorage<TextureObject> samplers = new ShaderStorage<>();
ShaderStorage<TextureObject> buffers = new ShaderStorage<>();
for(TextureBinding binding : textureProvider.get()) {
int location = GL20.glGetUniformLocation(program, binding.name());
if(location == -1) {
System.out.println("Couldn't find Location ["+binding.name()+"] in Program ["+program+"]");
//TODO Warn that location wasn't found
continue;
}
samplers.add(binding.name(), binding.slot(), new SamplerObject(binding.slot()));
(binding.mode() == TextureMode.SAMPLER ? samplers : buffers).add(binding.name(), binding.slot(), new TextureObject(binding.slot()));
}
for(UniformBinding binding : pipeline.uniforms()) {
for(BufferBinding binding : uniformProvider.get()) {
int location = GL31.glGetUniformBlockIndex(program, binding.name());
if(location == GL31.GL_INVALID_INDEX) {
System.out.println("Couldn't find Location ["+binding.name()+"] in Program ["+program+"]");
//TODO Warn that location wasn't found
continue;
}
GL31.glUniformBlockBinding(program, location, binding.slot());
uniforms.add(binding.name(), binding.slot(), new UniformObject(binding.slot()));
(binding.mode() == BufferMode.UNIFORM ? uniforms : storages).add(binding.name(), binding.slot(), new BufferObject(binding.slot()));
}
ShaderInstance instance = new ShaderInstance(program, uniforms, samplers);
states.shaders.register(instance);
return instance;
return new ShaderInstance(program, uniforms, storages, samplers, buffers);
}
private int computeShader(ID id, IAsset asset, ShaderType type, IAssetProvider provider) {
@@ -272,12 +327,12 @@ public class GLGraphicsDevice implements GraphicsDevice {
return shader;
}
private int loadOrCreateProgram(ShaderPipeline pipeline, MultiAsset assets, IntPredicate callback) {
private int loadOrCreateProgram(ID pipeline, Supplier<String> crc, IntPredicate callback) {
int id = GL20.glCreateProgram();
boolean fail = true;
if((fail = !loadFromCache(id, pipeline, assets) && callback.test(id))) {
if((fail = !loadFromCache(id, pipeline, crc) && callback.test(id))) {
if(GL20.glGetProgrami(id, GL20.GL_LINK_STATUS) == GL11.GL_TRUE) {
cache.store(pipeline.id(), assets.crc(), getProgramBytes(id));
cache.store(pipeline, crc.get(), getProgramBytes(id));
fail = false;
}
}
@@ -303,8 +358,8 @@ public class GLGraphicsDevice implements GraphicsDevice {
return data;
}
private boolean loadFromCache(int programId, ShaderPipeline pipeline, MultiAsset asset) {
ByteBuffer data = cache.get(pipeline.id(), asset::crc);
private boolean loadFromCache(int programId, ID pipeline, Supplier<String> crc) {
ByteBuffer data = cache.get(pipeline, crc);
if(data == null) return false;
GL41.glProgramBinary(programId, data.getInt(), data);
MemoryUtil.memFree(data);
@@ -5,10 +5,11 @@ import java.util.Objects;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.GL45;
import speiger.src.coreengine.graphics.api.buffer.VertexBuffer;
import speiger.src.coreengine.graphics.api.buffer.states.BufferType;
import speiger.src.coreengine.graphics.api.core.GraphicsCommandBuffer;
import speiger.src.coreengine.graphics.api.mesh.Mesh;
import speiger.src.coreengine.graphics.api.sampler.Sampler;
@@ -17,6 +18,7 @@ import speiger.src.coreengine.graphics.api.shader.DepthTarget;
import speiger.src.coreengine.graphics.api.shader.RasterizerState;
import speiger.src.coreengine.graphics.api.shader.ShaderPipeline;
import speiger.src.coreengine.graphics.api.texture.Texture;
import speiger.src.coreengine.graphics.api.utils.AccessType;
import speiger.src.coreengine.graphics.api.utils.ScissorsManager;
import speiger.src.coreengine.graphics.opengl.buffer.GLVertexBuffer;
import speiger.src.coreengine.graphics.opengl.mesh.GLMesh;
@@ -110,8 +112,7 @@ public class GLImmidateCommandBuffer implements GraphicsCommandBuffer {
ensureDrawing();
if(this.mesh == mesh) return this;
this.mesh = (GLMesh)mesh;
this.mesh.bind();
// GL30.glBindVertexArray(this.mesh.id());
GL30.glBindVertexArray(this.mesh.id());
recorded++;
return this;
}
@@ -123,11 +124,21 @@ public class GLImmidateCommandBuffer implements GraphicsCommandBuffer {
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.samplers().byIndex(binding), "Texture["+binding+"] binding not found");
// GLStates states = device.states;
GL45.glBindTextureUnit(binding, ((GLTexture)texture).id());
GL33.glBindSampler(binding, ((GLSampler)sampler).id());
// states.textures.bind(binding, ((GLTexture)texture).id());
// states.samplers.bind(binding, ((GLSampler)sampler).id());
GLStates states = device.states;
states.textures.bind(binding, ((GLTexture)texture).id());
states.samplers.bind(binding, ((GLSampler)sampler).id());
recorded++;
return this;
}
@Override
public GraphicsCommandBuffer texture(int binding, Texture texture, AccessType access) {
Objects.requireNonNull(texture);
Objects.requireNonNull(access);
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.buffers().byIndex(binding), "Texture["+binding+"] binding not found");
GL45.glBindImageTexture(0, binding, ((GLTexture)texture).id(), false, 0, GLUtils.toGL(access), GLUtils.toGLInternal(texture.settings().internal()));
recorded++;
return this;
}
@@ -135,6 +146,7 @@ public class GLImmidateCommandBuffer implements GraphicsCommandBuffer {
@Override
public GraphicsCommandBuffer uniform(int binding, VertexBuffer buffer) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.UNIFORM_BUFFER) throw new IllegalStateException("Buffer isn't a Uniform Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.uniforms().byIndex(binding), "Uniform for binding ["+binding+"] doesn't exist");
@@ -146,6 +158,7 @@ public class GLImmidateCommandBuffer implements GraphicsCommandBuffer {
@Override
public GraphicsCommandBuffer uniform(int binding, VertexBuffer buffer, long offset, long size) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.UNIFORM_BUFFER) throw new IllegalStateException("Buffer isn't a Uniform Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.uniforms().byIndex(binding), "Uniform for binding ["+binding+"] doesn't exist");
@@ -154,6 +167,30 @@ public class GLImmidateCommandBuffer implements GraphicsCommandBuffer {
return this;
}
@Override
public GraphicsCommandBuffer storage(int binding, VertexBuffer buffer) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.SHADER_STORAGE_BUFFER) throw new IllegalStateException("Buffer isn't a Shader Storage Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.storages().byIndex(binding), "Shader Storage for binding ["+binding+"] doesn't exist");
GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, binding, ((GLVertexBuffer)buffer).id());
recorded++;
return this;
}
@Override
public GraphicsCommandBuffer storage(int binding, VertexBuffer buffer, long offset, long size) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.SHADER_STORAGE_BUFFER) throw new IllegalStateException("Buffer isn't a Shader Storage Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.storages().byIndex(binding), "Shader Storage for binding ["+binding+"] doesn't exist");
GL30.glBindBufferRange(GL43.GL_SHADER_STORAGE_BUFFER, binding, ((GLVertexBuffer)buffer).id(), offset, size);
recorded++;
return this;
}
@Override
public GraphicsCommandBuffer drawArrays(int offset, int count) {
ensureDrawing();
@@ -0,0 +1,192 @@
package speiger.src.coreengine.graphics.opengl.core;
import java.util.Objects;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL42;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.GL45;
import speiger.src.coreengine.graphics.api.buffer.VertexBuffer;
import speiger.src.coreengine.graphics.api.buffer.states.BufferType;
import speiger.src.coreengine.graphics.api.compute.BarrierType;
import speiger.src.coreengine.graphics.api.compute.ComputePipeline;
import speiger.src.coreengine.graphics.api.core.ComputeCommandBuffer;
import speiger.src.coreengine.graphics.api.sampler.Sampler;
import speiger.src.coreengine.graphics.api.texture.Texture;
import speiger.src.coreengine.graphics.api.utils.AccessType;
import speiger.src.coreengine.graphics.opengl.buffer.GLVertexBuffer;
import speiger.src.coreengine.graphics.opengl.sampler.GLSampler;
import speiger.src.coreengine.graphics.opengl.shader.ShaderInstance;
import speiger.src.coreengine.graphics.opengl.texture.GLTexture;
import speiger.src.coreengine.graphics.opengl.utils.GLStates;
import speiger.src.coreengine.graphics.opengl.utils.GLUtils;
public class GLImmidateComputeCommandBuffer implements ComputeCommandBuffer {
GLGraphicsDevice device;
int recorded = 0;
boolean recording = false;
boolean removed = false;
ComputePipeline pipeline;
ShaderInstance shader;
public GLImmidateComputeCommandBuffer(GLGraphicsDevice device) {
this.device = device;
}
protected void ensureDrawing() {
if(removed) throw new IllegalStateException("CommandBuffer is removed");
if(!recording) throw new IllegalStateException("CommandBuffer isn't recording");
}
@Override
public boolean isRemoved() { return removed; }
@Override
public void remove() {
clear();
device = null;
removed = true;
recorded = 0;
}
@Override
public ComputeCommandBuffer begin() {
if(removed) throw new IllegalStateException("CommandBuffer is removed");
if(recording) throw new IllegalStateException("CommandBuffer already recording");
recording = true;
recorded = 0;
return this;
}
@Override
public ComputeCommandBuffer pipeline(ComputePipeline pipeline) {
Objects.requireNonNull(pipeline);
ensureDrawing();
if(this.pipeline == pipeline) return this;
this.pipeline = pipeline;
shader = device.getShader(pipeline);
if(shader == null) {
//TODO implement logging
return this;
}
GLStates states = device.states;
states.shaders.bind(shader);
recorded++;
return this;
}
@Override
public ComputeCommandBuffer texture(int binding, Texture texture, Sampler sampler) {
Objects.requireNonNull(texture);
Objects.requireNonNull(sampler);
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.samplers().byIndex(binding), "Texture["+binding+"] binding not found");
GLStates states = device.states;
states.textures.bind(binding, ((GLTexture)texture).id());
states.samplers.bind(binding, ((GLSampler)sampler).id());
recorded++;
return this;
}
@Override
public ComputeCommandBuffer texture(int binding, Texture texture, AccessType access) {
Objects.requireNonNull(texture);
Objects.requireNonNull(access);
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.buffers().byIndex(binding), "Texture["+binding+"] binding not found");
GL45.glBindImageTexture(0, binding, ((GLTexture)texture).id(), false, 0, GLUtils.toGL(access), GLUtils.toGLInternal(texture.settings().internal()));
recorded++;
return this;
}
@Override
public ComputeCommandBuffer uniform(int binding, VertexBuffer buffer) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.UNIFORM_BUFFER) throw new IllegalStateException("Buffer isn't a Uniform Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.uniforms().byIndex(binding), "Uniform for binding ["+binding+"] doesn't exist");
GL30.glBindBufferBase(GL31.GL_UNIFORM_BUFFER, binding, ((GLVertexBuffer)buffer).id());
recorded++;
return this;
}
@Override
public ComputeCommandBuffer uniform(int binding, VertexBuffer buffer, long offset, long size) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.UNIFORM_BUFFER) throw new IllegalStateException("Buffer isn't a Uniform Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.uniforms().byIndex(binding), "Uniform for binding ["+binding+"] doesn't exist");
GL30.glBindBufferRange(GL31.GL_UNIFORM_BUFFER, binding, ((GLVertexBuffer)buffer).id(), offset, size);
recorded++;
return this;
}
@Override
public ComputeCommandBuffer storage(int binding, VertexBuffer buffer) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.SHADER_STORAGE_BUFFER) throw new IllegalStateException("Buffer isn't a Shader Storage Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.storages().byIndex(binding), "Shader Storage for binding ["+binding+"] doesn't exist");
GL30.glBindBufferBase(GL43.GL_SHADER_STORAGE_BUFFER, binding, ((GLVertexBuffer)buffer).id());
recorded++;
return this;
}
@Override
public ComputeCommandBuffer storage(int binding, VertexBuffer buffer, long offset, long size) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.SHADER_STORAGE_BUFFER) throw new IllegalStateException("Buffer isn't a Shader Storage Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.storages().byIndex(binding), "Shader Storage for binding ["+binding+"] doesn't exist");
GL30.glBindBufferRange(GL43.GL_SHADER_STORAGE_BUFFER, binding, ((GLVertexBuffer)buffer).id(), offset, size);
recorded++;
return this;
}
@Override
public ComputeCommandBuffer dispatch(int numGroupsX, int numGroupsY, int numGroupsZ) {
Objects.requireNonNull(shader);
if(numGroupsX < 1) throw new IllegalArgumentException("X has to be 1 or more");
if(numGroupsY < 1) throw new IllegalArgumentException("Y has to be 1 or more");
if(numGroupsZ < 1) throw new IllegalArgumentException("Z has to be 1 or more");
GL45.glDispatchCompute(numGroupsX, numGroupsY, numGroupsZ);
return this;
}
@Override
public ComputeCommandBuffer barrier(BarrierType... types) {
int result = 0;
for(BarrierType type : types) {
result |= GLUtils.toGL(type);
}
GL42.glMemoryBarrier(result);
return this;
}
@Override
public ComputeCommandBuffer end() {
clear();
return this;
}
private void clear() {
recording = false;
pipeline = null;
shader = null;
}
@Override
public int commandCount() {
return recorded;
}
}
@@ -6,9 +6,12 @@ import java.util.Objects;
import org.lwjgl.opengl.GL11;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.GL45;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.coreengine.graphics.api.buffer.VertexBuffer;
import speiger.src.coreengine.graphics.api.buffer.states.BufferType;
import speiger.src.coreengine.graphics.api.buffer.states.IndeciesType;
import speiger.src.coreengine.graphics.api.core.GraphicsCommandBuffer;
import speiger.src.coreengine.graphics.api.mesh.Mesh;
@@ -19,6 +22,7 @@ import speiger.src.coreengine.graphics.api.shader.RasterizerState;
import speiger.src.coreengine.graphics.api.shader.ShaderPipeline;
import speiger.src.coreengine.graphics.api.shader.states.DrawMode;
import speiger.src.coreengine.graphics.api.texture.Texture;
import speiger.src.coreengine.graphics.api.utils.AccessType;
import speiger.src.coreengine.graphics.api.utils.ScissorsManager;
import speiger.src.coreengine.graphics.api.utils.ScissorsManager.NoOp;
import speiger.src.coreengine.graphics.opengl.buffer.GLVertexBuffer;
@@ -87,6 +91,10 @@ public class GLRecordingCommandBuffer implements GraphicsCommandBuffer {
if(this.pipeline == pipeline) return this;
this.pipeline = pipeline;
shader = device.getShader(pipeline);
if(shader == null) {
//TODO implement logging
return this;
}
tasks.add(new SetPipeline(pipeline, shader, device.states));
return this;
}
@@ -112,26 +120,61 @@ public class GLRecordingCommandBuffer implements GraphicsCommandBuffer {
return this;
}
@Override
public GraphicsCommandBuffer texture(int binding, Texture texture, AccessType access) {
Objects.requireNonNull(texture);
Objects.requireNonNull(access);
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.buffers().byIndex(binding), "Texture["+binding+"] binding not found");
tasks.add(new SetImageStorage(binding, (GLTexture)texture, access));
return this;
}
@Override
public GraphicsCommandBuffer uniform(int binding, VertexBuffer buffer) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.UNIFORM_BUFFER) throw new IllegalStateException("Buffer isn't a Uniform Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.uniforms().byIndex(binding), "Uniform for binding ["+binding+"] doesn't exist");
tasks.add(new SetUniform(binding, (GLVertexBuffer)buffer, -1, -1));
tasks.add(new SetUniform(binding, (GLVertexBuffer)buffer, -1, -1, false));
return this;
}
@Override
public GraphicsCommandBuffer uniform(int binding, VertexBuffer buffer, long offset, long size) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.UNIFORM_BUFFER) throw new IllegalStateException("Buffer isn't a Uniform Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.uniforms().byIndex(binding), "Uniform for binding ["+binding+"] doesn't exist");
tasks.add(new SetUniform(binding, (GLVertexBuffer)buffer, offset, size));
tasks.add(new SetUniform(binding, (GLVertexBuffer)buffer, offset, size, false));
return this;
}
@Override
public GraphicsCommandBuffer storage(int binding, VertexBuffer buffer) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.SHADER_STORAGE_BUFFER) throw new IllegalStateException("Buffer isn't a Shader Storage Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.storages().byIndex(binding), "Shader Storage for binding ["+binding+"] doesn't exist");
tasks.add(new SetUniform(binding, (GLVertexBuffer)buffer, -1, -1, true));
return this;
}
@Override
public GraphicsCommandBuffer storage(int binding, VertexBuffer buffer, long offset, long size) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.SHADER_STORAGE_BUFFER) throw new IllegalStateException("Buffer isn't a Shader Storage Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.storages().byIndex(binding), "Shader Storage for binding ["+binding+"] doesn't exist");
tasks.add(new SetUniform(binding, (GLVertexBuffer)buffer, offset, size, true));
return this;
}
@Override
public GraphicsCommandBuffer drawArrays(int offset, int count) {
ensureDrawing();
@@ -229,11 +272,18 @@ public class GLRecordingCommandBuffer implements GraphicsCommandBuffer {
}
}
public record SetUniform(int binding, GLVertexBuffer buffer, long offset, long size) implements Runnable {
public record SetImageStorage(int binding, GLTexture texture, AccessType type) implements Runnable {
@Override
public void run() {
if(size < 0 || offset < 0) GL30.glBindBufferBase(GL31.GL_UNIFORM_BUFFER, binding, buffer.id());
else GL30.glBindBufferRange(GL31.GL_UNIFORM_BUFFER, binding, buffer.id(), offset, size);
GL45.glBindImageTexture(0, binding, texture.id(), false, 0, GLUtils.toGL(type), GLUtils.toGLInternal(texture.settings().internal()));
}
}
public record SetUniform(int binding, GLVertexBuffer buffer, long offset, long size, boolean shader) implements Runnable {
@Override
public void run() {
if(size < 0 || offset < 0) GL30.glBindBufferBase(shader ? GL43.GL_SHADER_STORAGE_BUFFER : GL31.GL_UNIFORM_BUFFER, binding, buffer.id());
else GL30.glBindBufferRange(shader ? GL43.GL_SHADER_STORAGE_BUFFER : GL31.GL_UNIFORM_BUFFER, binding, buffer.id(), offset, size);
}
}
}
@@ -0,0 +1,227 @@
package speiger.src.coreengine.graphics.opengl.core;
import java.util.List;
import java.util.Objects;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL31;
import org.lwjgl.opengl.GL42;
import org.lwjgl.opengl.GL43;
import org.lwjgl.opengl.GL45;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.coreengine.graphics.api.buffer.VertexBuffer;
import speiger.src.coreengine.graphics.api.buffer.states.BufferType;
import speiger.src.coreengine.graphics.api.compute.BarrierType;
import speiger.src.coreengine.graphics.api.compute.ComputePipeline;
import speiger.src.coreengine.graphics.api.core.ComputeCommandBuffer;
import speiger.src.coreengine.graphics.api.sampler.Sampler;
import speiger.src.coreengine.graphics.api.texture.Texture;
import speiger.src.coreengine.graphics.api.utils.AccessType;
import speiger.src.coreengine.graphics.opengl.buffer.GLVertexBuffer;
import speiger.src.coreengine.graphics.opengl.sampler.GLSampler;
import speiger.src.coreengine.graphics.opengl.shader.ShaderInstance;
import speiger.src.coreengine.graphics.opengl.texture.GLTexture;
import speiger.src.coreengine.graphics.opengl.utils.GLStates;
import speiger.src.coreengine.graphics.opengl.utils.GLUtils;
public class GLRecordingComputeCommandBuffer implements ComputeCommandBuffer {
List<Runnable> tasks = new ObjectArrayList<>();
boolean recording = false;
boolean removed = false;
GLGraphicsDevice device;
ComputePipeline pipeline;
ShaderInstance shader;
public GLRecordingComputeCommandBuffer(GLGraphicsDevice device) {
this.device = device;
}
protected void ensureDrawing() {
if(removed) throw new IllegalStateException("CommandBuffer is removed");
if(!recording) throw new IllegalStateException("CommandBuffer isn't recording");
}
@Override
public boolean isRemoved() {
return removed;
}
@Override
public void remove() {
removed = true;
clear();
recording = false;
tasks.clear();
}
@Override
public int commandCount() {
return tasks.size();
}
@Override
public ComputeCommandBuffer begin() {
if(removed) throw new IllegalStateException("CommandBuffer is removed");
if(recording) throw new IllegalStateException("CommandBuffer already recording");
recording = true;
tasks.clear();
return this;
}
@Override
public ComputeCommandBuffer pipeline(ComputePipeline pipeline) {
Objects.requireNonNull(pipeline);
ensureDrawing();
if(this.pipeline == pipeline) return this;
this.pipeline = pipeline;
shader = device.getShader(pipeline);
if(shader == null) {
//TODO implement logging
return this;
}
tasks.add(new SetPipeline(shader, device.states));
return this;
}
@Override
public ComputeCommandBuffer texture(int binding, Texture texture, Sampler sampler) {
Objects.requireNonNull(texture);
Objects.requireNonNull(sampler);
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.samplers().byIndex(binding), "Texture["+binding+"] binding not found");
tasks.add(new SetTexture(binding, (GLTexture)texture, (GLSampler)sampler, device.states));
return this;
}
@Override
public ComputeCommandBuffer texture(int binding, Texture texture, AccessType access) {
Objects.requireNonNull(texture);
Objects.requireNonNull(access);
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.buffers().byIndex(binding), "Texture["+binding+"] binding not found");
tasks.add(new SetImageStorage(binding, (GLTexture)texture, access));
return this;
}
@Override
public ComputeCommandBuffer uniform(int binding, VertexBuffer buffer) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.UNIFORM_BUFFER) throw new IllegalStateException("Buffer isn't a Uniform Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.uniforms().byIndex(binding), "Uniform for binding ["+binding+"] doesn't exist");
tasks.add(new SetUniform(binding, (GLVertexBuffer)buffer, -1, -1, false));
return this;
}
@Override
public ComputeCommandBuffer uniform(int binding, VertexBuffer buffer, long offset, long size) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.UNIFORM_BUFFER) throw new IllegalStateException("Buffer isn't a Uniform Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.uniforms().byIndex(binding), "Uniform for binding ["+binding+"] doesn't exist");
tasks.add(new SetUniform(binding, (GLVertexBuffer)buffer, offset, size, false));
return this;
}
@Override
public ComputeCommandBuffer storage(int binding, VertexBuffer buffer) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.SHADER_STORAGE_BUFFER) throw new IllegalStateException("Buffer isn't a Shader Storage Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.storages().byIndex(binding), "Shader Storage for binding ["+binding+"] doesn't exist");
tasks.add(new SetUniform(binding, (GLVertexBuffer)buffer, -1, -1, true));
return this;
}
@Override
public ComputeCommandBuffer storage(int binding, VertexBuffer buffer, long offset, long size) {
Objects.requireNonNull(buffer);
if(buffer.type() != BufferType.SHADER_STORAGE_BUFFER) throw new IllegalStateException("Buffer isn't a Shader Storage Buffer");
ensureDrawing();
Objects.requireNonNull(shader, "No Pipeline found");
Objects.requireNonNull(shader.storages().byIndex(binding), "Shader Storage for binding ["+binding+"] doesn't exist");
tasks.add(new SetUniform(binding, (GLVertexBuffer)buffer, offset, size, true));
return this;
}
@Override
public ComputeCommandBuffer dispatch(int numGroupsX, int numGroupsY, int numGroupsZ) {
Objects.requireNonNull(shader);
if(numGroupsX < 1) throw new IllegalArgumentException("X has to be 1 or more");
if(numGroupsY < 1) throw new IllegalArgumentException("Y has to be 1 or more");
if(numGroupsZ < 1) throw new IllegalArgumentException("Z has to be 1 or more");
tasks.add(() -> GL45.glDispatchCompute(numGroupsX, numGroupsY, numGroupsZ));
return this;
}
@Override
public ComputeCommandBuffer barrier(BarrierType... types) {
int result = 0;
for(BarrierType type : types) {
result |= GLUtils.toGL(type);
}
tasks.add(new SetBarrier(result));
return this;
}
@Override
public ComputeCommandBuffer end() {
if(!recording) throw new IllegalStateException("Already Stopped Recording");
clear();
return this;
}
public void execute() {
tasks.forEach(Runnable::run);
}
private void clear() {
recording = false;
pipeline = null;
shader = null;
}
public record SetPipeline(ShaderInstance instance, GLStates states) implements Runnable {
@Override
public void run() {
states.shaders.bind(instance);
}
}
public record SetTexture(int binding, GLTexture texture, GLSampler sampler, GLStates states) implements Runnable {
@Override
public void run() {
states.textures.bind(binding, texture.id());
states.samplers.bind(binding, sampler.id());
}
}
public record SetImageStorage(int binding, GLTexture texture, AccessType type) implements Runnable {
@Override
public void run() {
GL45.glBindImageTexture(0, binding, texture.id(), false, 0, GLUtils.toGL(type), GLUtils.toGLInternal(texture.settings().internal()));
}
}
public record SetUniform(int binding, GLVertexBuffer buffer, long offset, long size, boolean shader) implements Runnable {
@Override
public void run() {
if(size < 0 || offset < 0) GL30.glBindBufferBase(shader ? GL43.GL_SHADER_STORAGE_BUFFER : GL31.GL_UNIFORM_BUFFER, binding, buffer.id());
else GL30.glBindBufferRange(shader ? GL43.GL_SHADER_STORAGE_BUFFER : GL31.GL_UNIFORM_BUFFER, binding, buffer.id(), offset, size);
}
}
public record SetBarrier(int barrierBits) implements Runnable {
@Override
public void run() {
GL42.glMemoryBarrier(barrierBits);
}
}
}
@@ -1,16 +1,19 @@
package speiger.src.coreengine.graphics.opengl.core;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL;
import org.lwjgl.opengl.GL11;
import speiger.src.coreengine.graphics.api.core.GraphicsSurface;
import speiger.src.coreengine.rendering.input.window.Window;
import speiger.src.coreengine.input.window.Window;
public class GLSurface implements GraphicsSurface {
GLGraphicsDevice device;
Window window;
public GLSurface(Window window) {
public GLSurface(Window window, GLGraphicsDevice device) {
this.window = window;
this.device = device;
}
@Override
@@ -23,12 +26,14 @@ public class GLSurface implements GraphicsSurface {
GL11.glClearColor(0.2F, 0.55F, 0.66F, 1F);
GL11.glClear(GL11.GL_COLOR_BUFFER_BIT | GL11.GL_DEPTH_BUFFER_BIT);
window.beginFrame();
GLFW.glfwPollEvents();
GL.setCapabilities(device.capabilities);
if(window.isPrimaryWindow()) window.manager().joinOnWindowThread(GLFW::glfwPollEvents);
Thread.onSpinWait();
window.handleInput();
}
@Override
public void finishFrame() {
window.handleInput();
window.finishFrame();
}
@@ -0,0 +1,50 @@
package speiger.src.coreengine.graphics.opengl.core;
import java.util.OptionalLong;
import org.lwjgl.opengl.GL15;
import org.lwjgl.opengl.GL33;
import org.lwjgl.opengl.GL45;
import speiger.src.coreengine.graphics.api.core.TimeQueryPool;
public class GLTimeQueryPool implements TimeQueryPool {
boolean removed = false;
int[] queries;
public GLTimeQueryPool(int size) {
this.queries = new int[size];
GL45.glCreateQueries(GL33.GL_TIMESTAMP, queries);
}
@Override
public boolean isRemoved() {
return removed;
}
@Override
public void remove() {
if(removed) return;
GL15.glDeleteQueries(queries);
removed = true;
}
@Override
public int size() {
return queries.length;
}
@Override
public void write(int index) {
GL33.glQueryCounter(queries[index], GL33.GL_TIMESTAMP);
}
@Override
public OptionalLong get(int index) {
if(GL15.glGetQueryObjectui(queries[index], GL15.GL_QUERY_RESULT_AVAILABLE) == 1) {
return OptionalLong.of(GL33.glGetQueryObjectui64(queries[index], GL15.GL_QUERY_RESULT));
}
return OptionalLong.empty();
}
}
@@ -1,7 +1,6 @@
package speiger.src.coreengine.graphics.opengl.mesh;
import org.jspecify.annotations.Nullable;
import org.lwjgl.opengl.GL30;
import org.lwjgl.opengl.GL45;
import speiger.src.collections.ints.collections.IntIterator;
@@ -34,13 +33,6 @@ public class GLMesh extends Mesh {
return vao;
}
public void bind() {
GL30.glBindVertexArray(vao);
for(Element element : layouts.values().map(LayoutInfo::layout).flatMap(VertexLayout::elements)) {
GL45.glEnableVertexArrayAttrib(vao, element.index());
}
}
@Override
public boolean equals(Object obj) {
return obj instanceof GLMesh mesh && mesh.vao == vao;
@@ -79,6 +71,7 @@ public class GLMesh extends Mesh {
for(Element element : layout) {
GL45.glVertexArrayAttribFormat(vao, element.index(), element.size(), GLUtils.toGL(element.type()), element.normalized(), layout.offset(index++));
GL45.glVertexArrayAttribBinding(vao, element.index(), binding);
GL45.glEnableVertexArrayAttrib(vao, element.index());
}
}
@@ -6,9 +6,9 @@ import speiger.src.collections.ints.maps.impl.hash.Int2ObjectOpenHashMap;
import speiger.src.collections.ints.maps.interfaces.Int2ObjectMap;
import speiger.src.collections.objects.maps.impl.hash.Object2ObjectOpenHashMap;
public record ShaderInstance(int programId, ShaderStorage<UniformObject> uniforms, ShaderStorage<SamplerObject> samplers) {
public record UniformObject(int slot) {}
public record SamplerObject(int unit) {}
public record ShaderInstance(int programId, ShaderStorage<BufferObject> uniforms, ShaderStorage<BufferObject> storages, ShaderStorage<TextureObject> samplers, ShaderStorage<TextureObject> buffers) {
public record BufferObject(int slot) {}
public record TextureObject(int unit) {}
public static class ShaderStorage<T> {
Map<String, T> byName = new Object2ObjectOpenHashMap<>();
@@ -2,21 +2,9 @@ package speiger.src.coreengine.graphics.opengl.shader;
import org.lwjgl.opengl.GL20;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.lists.ObjectList;
public class ShaderTracker {
ObjectList<ShaderInstance> knownShaders = new ObjectArrayList<ShaderInstance>();
int boundShader;
public void register(ShaderInstance instance) {
knownShaders.add(instance);
}
public void remove(ShaderInstance instance) {
knownShaders.remove(instance);
}
public void bind(ShaderInstance instance) {
int id = instance.programId();
if(id == boundShader) return;
@@ -18,6 +18,7 @@ import org.lwjgl.opengl.GL44;
import speiger.src.coreengine.graphics.api.buffer.states.BufferState;
import speiger.src.coreengine.graphics.api.buffer.states.BufferType;
import speiger.src.coreengine.graphics.api.buffer.states.IndeciesType;
import speiger.src.coreengine.graphics.api.compute.BarrierType;
import speiger.src.coreengine.graphics.api.sampler.states.BorderMode;
import speiger.src.coreengine.graphics.api.sampler.states.SampleMode;
import speiger.src.coreengine.graphics.api.shader.states.BlendFactor;
@@ -29,6 +30,7 @@ import speiger.src.coreengine.graphics.api.texture.states.StencilType;
import speiger.src.coreengine.graphics.api.texture.states.SwizzleMask;
import speiger.src.coreengine.graphics.api.texture.states.TextureFormat;
import speiger.src.coreengine.graphics.api.texture.states.TextureType;
import speiger.src.coreengine.graphics.api.utils.AccessType;
import speiger.src.coreengine.graphics.api.utils.AlphaFunction;
public class GLUtils {
@@ -119,6 +121,14 @@ public class GLUtils {
};
}
public static int toGL(AccessType type) {
return switch(type) {
case READ -> GL15.GL_READ_ONLY;
case WRITE -> GL15.GL_WRITE_ONLY;
case BOTH -> GL15.GL_READ_WRITE;
};
}
public static int toGL(SwizzleMask mask) {
return switch(mask) {
case RED -> GL11.GL_RED;
@@ -232,7 +242,7 @@ public class GLUtils {
case GEOMETRY -> GL32.GL_GEOMETRY_SHADER;
case TESSELATION_CONTROL -> GL40.GL_TESS_CONTROL_SHADER;
case TESSELATION_EVALUATION -> GL40.GL_TESS_EVALUATION_SHADER;
case COMPUTE -> GL43.GL_COMPUTE_SHADER;
};
}
@@ -256,4 +266,17 @@ public class GLUtils {
case INT -> GL11.GL_UNSIGNED_INT;
};
}
public static int toGL(BarrierType type) {
return switch(type) {
case MESH_BUFFER -> GL42.GL_VERTEX_ATTRIB_ARRAY_BARRIER_BIT | GL42.GL_ELEMENT_ARRAY_BARRIER_BIT;
case SHADER_TEXTURE -> GL42.GL_TEXTURE_FETCH_BARRIER_BIT;
case SHADER_IMAGE -> GL42.GL_SHADER_IMAGE_ACCESS_BARRIER_BIT;
case SHADER_STORAGE -> GL43.GL_SHADER_STORAGE_BARRIER_BIT;
case SHADER_UNIFORM -> GL42.GL_UNIFORM_BARRIER_BIT;
case INDIRECT_COMMAND -> GL42.GL_COMMAND_BARRIER_BIT;
case FRAMEBUFFER -> GL42.GL_FRAMEBUFFER_BARRIER_BIT;
case CPU -> GL42.GL_BUFFER_UPDATE_BARRIER_BIT | GL44.GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT;
};
}
}
@@ -0,0 +1,76 @@
package speiger.src.coreengine.input.device;
import java.util.Deque;
import java.util.Objects;
import java.util.concurrent.ConcurrentLinkedDeque;
import speiger.src.collections.longs.collections.LongIterable;
import speiger.src.collections.longs.maps.impl.concurrent.Long2ObjectConcurrentOpenHashMap;
import speiger.src.collections.longs.maps.interfaces.Long2ObjectMap;
import speiger.src.coreengine.input.window.IWindowListener.Reason;
import speiger.src.coreengine.input.window.Window;
import speiger.src.coreengine.utils.eventbus.Event;
import speiger.src.coreengine.utils.eventbus.EventBus;
public abstract class AbstractDevice<T, E> implements InputDevice {
protected Long2ObjectMap<Deque<E>> queues = new Long2ObjectConcurrentOpenHashMap<>();
protected Long2ObjectMap<T> windowData = new Long2ObjectConcurrentOpenHashMap<>();
protected EventBus bus;
public void init(EventBus bus) {
this.bus = bus;
}
protected LongIterable knownWindows() {
return queues.keySet();
}
public void register(Window window) {
registerCallbacks(window);
long id = window.id();
queues.putIfAbsent(id, new ConcurrentLinkedDeque<>());
T data = createData(id);
if(data == null) return;
windowData.put(id, data);
}
protected void registerCallbacks(Window window) {
window.addListener(this::onClose);
}
private void onClose(Window window, Reason reason) {
if(reason == Reason.CLOSING) {
queues.remove(window.id());
windowData.remove(window.id());
}
}
protected abstract T createData(long windowId);
protected abstract void process(E task);
protected boolean pushEvent(Event event) {
if(bus == null) return true;
bus.post(event);
return event.isCancelable() && event.isCanceled();
}
protected void push(long windowId, E task) {
Objects.requireNonNull(task);
Deque<E> queue = queues.get(windowId);
if(queue == null) return;
queue.add(task);
}
@Override
public void processInput(long windowId) {
Deque<E> queue = queues.get(windowId);
if(queue == null) return;
while(!queue.isEmpty()) {
process(queue.poll());
}
}
public T get(long windowId) {
return windowData.get(windowId);
}
}
@@ -0,0 +1,47 @@
package speiger.src.coreengine.input.device;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWDropCallback;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.coreengine.input.device.FileDrop.DropTask;
import speiger.src.coreengine.input.events.FileEvents;
import speiger.src.coreengine.input.window.Window;
public class FileDrop extends AbstractDevice<Void, DropTask> {
public static final FileDrop INSTANCE = new FileDrop();
@Override
protected void registerCallbacks(Window window) {
super.registerCallbacks(window);
window.addCallback(this::drop, GLFW::glfwSetDropCallback);
}
private void drop(long windowId, int count, long names) {
List<Path> paths = new ObjectArrayList<>(count);
for(int i = 0;i<count;i++) {
Path path = Path.of(GLFWDropCallback.getName(names, i));
if(Files.notExists(path)) continue;
paths.add(path);
}
if(paths.isEmpty()) return;
push(windowId, new DropTask(windowId, paths.toArray(Path[]::new)));
}
@Override
public void reset(long windowId) {}
@Override
protected Void createData(long windowId) { return null; }
@Override
protected void process(DropTask task) {
int x = Mouse.INSTANCE.x(task.windowId());
int y = Mouse.INSTANCE.y(task.windowId());
pushEvent(new FileEvents.Drop(task.windowId(), x, y, task.files()));
}
public record DropTask(long windowId, Path[] files) {}
}
@@ -0,0 +1,9 @@
package speiger.src.coreengine.input.device;
import speiger.src.coreengine.input.window.Window;
public interface InputDevice {
public void register(Window window);
public void processInput(long windowId);
public void reset(long windowId);
}
@@ -0,0 +1,139 @@
package speiger.src.coreengine.input.device;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.util.BitSet;
import org.lwjgl.glfw.GLFW;
import speiger.src.collections.floats.lists.FloatArrayList;
import speiger.src.collections.floats.lists.FloatList;
import speiger.src.collections.ints.collections.IntIterator;
import speiger.src.collections.ints.maps.impl.hash.Int2ObjectOpenHashMap;
import speiger.src.collections.ints.maps.interfaces.Int2ObjectMap;
import speiger.src.collections.ints.sets.IntLinkedOpenHashSet;
import speiger.src.collections.ints.sets.IntSet;
import speiger.src.collections.longs.collections.LongIterator;
import speiger.src.collections.longs.sets.LongOpenHashSet;
import speiger.src.collections.longs.sets.LongSet;
import speiger.src.coreengine.input.device.Joystick.JoyStickData;
import speiger.src.coreengine.input.device.Joystick.JoyStickTask;
import speiger.src.coreengine.input.events.JoystickEvent;
import speiger.src.coreengine.input.window.WindowManager;
import speiger.src.coreengine.utils.eventbus.EventBus;
public class Joystick extends AbstractDevice<JoyStickData, JoyStickTask> {
public static final Joystick INSTANCE = new Joystick();
WindowManager manager;
IntSet presentJoysticks = new IntLinkedOpenHashSet();
LongSet alwaysProcessInputs = new LongOpenHashSet();
public void init(WindowManager manager, EventBus bus) {
this.manager = manager;
super.init(bus);
manager.addCallback(this::plugin, GLFW::glfwSetJoystickCallback);
for(int i = 0,m=GLFW.GLFW_JOYSTICK_LAST;i<=m;i++) {
if(GLFW.glfwJoystickPresent(i)) {
plugin(i, GLFW.GLFW_CONNECTED);
}
}
}
@Override
public void init(EventBus bus) { throw new UnsupportedOperationException("Use init(WindowManager, EventBus) instead"); }
public void setWindowAlwaysProcessInputs(long windowId, boolean value) {
if(value) alwaysProcessInputs.add(windowId);
else alwaysProcessInputs.remove(windowId);
}
public boolean isWindowAlwaysProcessingInputs(long windowId) {
return alwaysProcessInputs.contains(windowId);
}
private void plugin(int jid, int event) {
if(event == GLFW.GLFW_CONNECTED) presentJoysticks.add(jid);
else presentJoysticks.remove(jid);
for(LongIterator iter = knownWindows().iterator();iter.hasNext();) {
long window = iter.nextLong();
push(window, new Plugin(window, jid, event));
}
}
@Override
public void reset(long windowId) {}
@Override
protected JoyStickData createData(long windowId) { return new JoyStickData(presentJoysticks); }
@Override
protected void process(JoyStickTask task) { task.process(this); }
@Override
public void processInput(long windowId) {
super.processInput(windowId);
if(manager.getActiveWindow() != windowId && !alwaysProcessInputs.contains(windowId)) return;
JoyStickData data = get(windowId);
if(data == null) return;
for(IntIterator iter = presentJoysticks.iterator();iter.hasNext();) {
int jid = iter.nextInt();
ButtonData buttons = data.data.get(jid);
if(buttons == null) continue;
handleJoystick(windowId, jid, buttons);
}
}
public void handleJoystick(long window, int jid, ButtonData data) {
ByteBuffer buttons = GLFW.glfwGetJoystickButtons(jid);
for(int i = 0;buttons.hasRemaining();i++) {
boolean state = buttons.get() == GLFW.GLFW_PRESS;
if(data.buttons.get(i) != state) {
data.buttons.set(i, state);
pushEvent(new JoystickEvent.Button(window, jid, i, state));
}
}
FloatBuffer axis = GLFW.glfwGetJoystickAxes(jid);
for(int i = 0;axis.hasRemaining();i++) {
float state = axis.get();
if(i >= data.axis.size()) data.axis.add(state);
else data.axis.set(i, state);
boolean negative = state < 0F;
boolean max = state < -0.95F || state > 0.95F;
boolean min = (state < -0.5F || state > 0.5F) && !max;
if(data.axisState.get(i*3) != negative || data.axisState.get(i*3+1) != max || data.axisState.get(i*3+2) != min) {
data.axisState.set(i*3, negative);
data.axisState.set(i*3+1, max);
data.axisState.set(i*3+2, min);
pushEvent(new JoystickEvent.Axis(window, jid, i, state, negative, min, max));
}
}
}
public static class JoyStickData {
Int2ObjectMap<ButtonData> data = new Int2ObjectOpenHashMap<>();
public JoyStickData(IntSet knownJoysticks) {
for(IntIterator iter = knownJoysticks.iterator();iter.hasNext();) {
data.put(iter.nextInt(), new ButtonData());
}
}
}
public static class ButtonData {
BitSet buttons = new BitSet();
FloatList axis = new FloatArrayList();
BitSet axisState = new BitSet();
}
public static interface JoyStickTask {
public void process(Joystick stick);
}
public record Plugin(long window, int jid, int event) implements JoyStickTask {
@Override
public void process(Joystick stick) {
JoyStickData data = stick.get(window);
if(event == GLFW.GLFW_CONNECTED) data.data.put(jid, new ButtonData());
else if(event == GLFW.GLFW_DISCONNECTED) data.data.remove(jid);
stick.pushEvent(new JoystickEvent.Connected(window, jid, event == GLFW.GLFW_CONNECTED));
}
}
}
@@ -0,0 +1,75 @@
package speiger.src.coreengine.input.device;
import java.util.function.BiPredicate;
import org.lwjgl.glfw.GLFW;
import speiger.src.coreengine.input.device.Keyboard.KeyData;
import speiger.src.coreengine.input.device.Keyboard.KeyTask;
import speiger.src.coreengine.input.events.KeyEvent;
import speiger.src.coreengine.input.events.KeyEvent.Key;
import speiger.src.coreengine.input.window.Window;
public class Keyboard extends AbstractDevice<KeyData, KeyTask> {
public static final Keyboard INSTANCE = new Keyboard();
@Override
public void reset(long windowId) {}
@Override
protected KeyData createData(long windowId) { return new KeyData(); }
@Override
protected void process(KeyTask task) { task.process(this); }
@Override
protected void registerCallbacks(Window window) {
super.registerCallbacks(window);
window.addCallback(this::keyPress, GLFW::glfwSetKeyCallback);
window.addCallback(this::charTyped, GLFW::glfwSetCharCallback);
}
private void keyPress(long window, int key, int scancode, int action, int mods) { push(window, new KeyPressed(window, key, scancode, action, mods)); }
private void charTyped(long window, int codepoint) { push(window, new CharTyped(window, codepoint)); }
public static class KeyData {
BiPredicate<Integer, Keyboard> filter;
boolean[] pressedKeys = new boolean[350];
boolean[] nonConsumedKeys = new boolean[350];
void reset() {
filter = null;
}
}
public static interface KeyTask {
public void process(Keyboard board);
}
public record KeyPressed(long window, int key, int scancode, int action, int mods) implements KeyTask {
@Override
public void process(Keyboard board) {
if(key < 0 || key >= 350) return;
KeyData data = board.get(window);
if(action >= 1) {
data.pressedKeys[key] = true;
onKeyPressed(board, data);
return;
}
data.pressedKeys[key] = false;
data.nonConsumedKeys[key] = false;
board.pushEvent(new Key(window, key, scancode, false));
}
private void onKeyPressed(Keyboard board, KeyData data) {
if(data.filter == null || data.filter.test(key, board)) {
if(board.pushEvent(new Key(window, key, scancode, true))) return;
data.nonConsumedKeys[key] = true;
}
}
}
public record CharTyped(long window, int codepoint) implements KeyTask {
@Override
public void process(Keyboard board) {
board.pushEvent(new KeyEvent.Char(window, codepoint));
}
}
}
@@ -0,0 +1,145 @@
package speiger.src.coreengine.input.device;
import org.lwjgl.glfw.GLFW;
import speiger.src.collections.ints.sets.IntOpenHashSet;
import speiger.src.collections.ints.sets.IntSet;
import speiger.src.coreengine.input.device.Mouse.MouseData;
import speiger.src.coreengine.input.device.Mouse.MouseTask;
import speiger.src.coreengine.input.events.MouseEvent;
import speiger.src.coreengine.input.window.Window;
import speiger.src.coreengine.math.vector.ints.Vec2i;
public class Mouse extends AbstractDevice<MouseData, MouseTask> {
public static final Mouse INSTANCE = new Mouse();
@Override
protected MouseData createData(long windowId) { return new MouseData(); }
@Override
public void reset(long windowId) { get(windowId).reset(); }
protected void process(MouseTask task) { task.process(this); }
@Override
protected void registerCallbacks(Window window) {
super.registerCallbacks(window);
window.addCallback(this::move, GLFW::glfwSetCursorPosCallback);
window.addCallback(this::click, GLFW::glfwSetMouseButtonCallback);
window.addCallback(this::enter, GLFW::glfwSetCursorEnterCallback);
window.addCallback(this::scroll, GLFW::glfwSetScrollCallback);
}
//@formatter:off
private void move(long window, double x, double y) { push(window, new Move(window, x, y)); }
private void click(long window, int button, int action, int mods) { push(window, new Click(window, button, action, mods)); }
private void enter(long window, boolean enter) { push(window, new Enter(window, enter)); }
private void scroll(long window, double xoffset, double yoffset) { push(window, new Scroll(window, xoffset, yoffset)); }
//@formatter:on
public static class MouseData {
IntSet buttons = new IntOpenHashSet();
Vec2i position = Vec2i.mutable();
Vec2i motion = Vec2i.mutable();
Vec2i scroll = Vec2i.mutable();
boolean active = true;
void reset() {
motion.negate();
position.negate();
scroll.negate();
}
//@formatter:off
public int x() { return position.x(); }
public int y() { return position.y(); }
public boolean pressed(int button) { return buttons.contains(button); }
public Vec2i motion() { return motion; }
public Vec2i scroll() { return scroll; }
//@formatter:on
}
public int x(long windowId) {
MouseData data = get(windowId);
return data == null ? 0 : data.x();
}
public int y(long windowId) {
MouseData data = get(windowId);
return data == null ? 0 : data.y();
}
public boolean pressed(long windowId, int button) {
MouseData data = get(windowId);
return data != null && data.pressed(button);
}
public Vec2i motion(long windowId) {
MouseData data = get(windowId);
return data == null ? Vec2i.ZERO : data.motion();
}
public Vec2i scroll(long windowId) {
MouseData data = get(windowId);
return data == null ? Vec2i.ZERO : data.scroll();
}
public void setPosition(long windowId, int x, int y) {
MouseData data = get(windowId);
if(data == null || (data.position.x() == x && data.position.y() == y)) return;
data.position.set(x, y);
GLFW.glfwSetCursorPos(windowId, x, y);
}
public void pressButton(long window, int button, boolean press) { click(window, button, press ? GLFW.GLFW_PRESS : GLFW.GLFW_RELEASE, 0); }
public void scrollMouse(long window, int xScroll, int yScroll) { scroll(window, xScroll, yScroll); }
public interface MouseTask {
public void process(Mouse mouse);
}
private record Move(long window, double x, double y) implements MouseTask {
@Override
public void process(Mouse mouse) {
MouseData data = mouse.get(window);
int xOff = (int)(x - data.position.x());
int yOff = (int)(y - data.position.y());
data.position.set((int)x, (int)y);
if(!mouse.pushEvent(new MouseEvent.Move(window, (int)x, (int)y, xOff, yOff))) {
data.motion.add(xOff, yOff);
}
}
}
private record Click(long window, int button, int action, int mods) implements MouseTask {
@Override
public void process(Mouse mouse) {
MouseData data = mouse.get(window);
if(action == GLFW.GLFW_PRESS) data.buttons.add(action);
else if(action == GLFW.GLFW_RELEASE) data.buttons.remove(action);
mouse.pushEvent(new MouseEvent.Click(window, data.position.x(), data.position.y(), button, action == GLFW.GLFW_PRESS));
}
}
private record Scroll(long window, double x, double y) implements MouseTask {
@Override
public void process(Mouse mouse) {
MouseData data = mouse.get(window);
if(!mouse.pushEvent(new MouseEvent.Scroll(window, data.position.x(), data.position.y(), (int)x, (int)y))) {
data.scroll.add((int)x, (int)y);
}
}
}
private record Enter(long window, boolean enter) implements MouseTask {
@Override
public void process(Mouse mouse) {
MouseData data = mouse.get(window);
data.active = enter;
if(!enter) {
//TODO decide if this should also push phantom events clearing the pressed keys?
data.buttons.clear();
data.position.negate();
data.reset();
}
}
}
}
@@ -0,0 +1,33 @@
package speiger.src.coreengine.input.events;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Iterator;
import speiger.src.collections.objects.utils.ObjectIterators;
public class FileEvents {
public static class Drop extends MouseEvent implements Iterable<DroppedFile> {
DroppedFile[] files;
public Drop(long window, int x, int y, Path[] files) {
super(window, x, y);
this.files = new DroppedFile[files.length];
for(int i = 0,m=files.length;i<m;i++) {
String name = files[i].getFileName().toString();
this.files[i] = new DroppedFile(files[i], name, name.substring(name.lastIndexOf(".")+1));
}
}
@Override
public Iterator<DroppedFile> iterator() { return ObjectIterators.wrap(files); }
public int size() { return files.length; }
public DroppedFile get() { return files[0]; }
public DroppedFile get(int index) { return files[index]; }
}
public record DroppedFile(Path path, String name, String extension) {
public boolean isFolder() { return Files.isDirectory(path); }
public boolean isFile() { return Files.isRegularFile(path); }
}
}
@@ -0,0 +1,65 @@
package speiger.src.coreengine.input.events;
import speiger.src.coreengine.utils.eventbus.Event;
public class JoystickEvent extends Event {
final long window;
final int jid;
public JoystickEvent(long window, int jid) {
this.window = window;
this.jid = jid;
}
public long window() { return window; }
public int jid() { return jid; }
public static class Connected extends JoystickEvent {
boolean connected;
public Connected(long window, int jid, boolean connected) {
super(window, jid);
this.connected = connected;
}
public boolean connected() { return connected; }
}
public static class Button extends JoystickEvent {
final int button;
final boolean press;
public Button(long window, int jid, int button, boolean press) {
super(window, jid);
this.button = button;
this.press = press;
}
public int button() { return button; }
public boolean press() { return press; }
}
public static class Axis extends JoystickEvent {
int axis;
float value;
boolean negative;
boolean halfPress;
boolean fullPress;
public Axis(long window, int jid, int axis, float value, boolean negative, boolean halfPress, boolean fullPress) {
super(window, jid);
this.axis = axis;
this.value = value;
this.negative = negative;
this.halfPress = halfPress;
this.fullPress = fullPress;
}
public int axis() { return axis; }
public float value() { return value; }
public float absoluteValue() { return Math.abs(value); }
public boolean negative() { return negative; }
public boolean halfPress() { return halfPress; }
public boolean fullPress() { return fullPress; }
}
}
@@ -0,0 +1,43 @@
package speiger.src.coreengine.input.events;
import speiger.src.coreengine.utils.eventbus.Event;
public abstract class KeyEvent extends Event {
final long window;
public KeyEvent(long window) {
this.window = window;
}
@Override
public boolean isCancelable() { return true; }
public long window() { return window; }
public static class Key extends KeyEvent {
final int key;
final int scancode;
final boolean press;
public Key(long window, int key, int scancode, boolean press) {
super(window);
this.key = key;
this.scancode = scancode;
this.press = press;
}
public int key() { return key; }
public int scancode() { return scancode; }
public boolean press() { return press; }
}
public static class Char extends KeyEvent {
final int codepoint;
public Char(long window, int codepoint) {
super(window);
this.codepoint = codepoint;
}
public int codepoint() { return codepoint; }
}
}
@@ -0,0 +1,80 @@
package speiger.src.coreengine.input.events;
import speiger.src.coreengine.utils.eventbus.Event;
public abstract class MouseEvent extends Event {
final long window;
int x;
int y;
final int originX;
final int originY;
public MouseEvent(long window, int x, int y) {
this.window = window;
this.originX = this.x = x;
this.originY = this.y = y;
}
public long windowId() { return window; }
public int x() { return x; }
public void x(int x) { this.x = x; }
public int y() { return y; }
public void y(int y) { this.y = y; }
public int originX() { return originX; }
public int originY() { return originY; }
public void reset() {
x = originX;
y = originY;
}
//TODO implement support for Scaling
@Override
public boolean isCancelable() { return true; }
public boolean isForced() { return false; }
public static class Click extends MouseEvent {
final int button;
final boolean press;
public Click(long window, int x, int y, int button, boolean press) {
super(window, x, y);
this.button = button;
this.press = press;
}
@Override
public boolean isForced() { return !press; }
public int button() { return button; }
public boolean press() { return press; }
}
public static class Move extends MouseEvent {
final int xMove;
final int yMove;
public Move(long window, int x, int y, int xMove, int yMove) {
super(window, x, y);
this.xMove = xMove;
this.yMove = yMove;
}
public int xMove() { return xMove; }
public int yMove() { return yMove; }
}
public static class Scroll extends MouseEvent {
final int scrollX;
final int scrollY;
public Scroll(long window, int x, int y, int scrollX, int scrollY) {
super(window, x, y);
this.scrollX = scrollX;
this.scrollY = scrollY;
}
public int scrollX() { return scrollX; }
public int scrollY() { return scrollY; }
}
}
@@ -0,0 +1,39 @@
package speiger.src.coreengine.input.window;
import org.jspecify.annotations.NonNull;
import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.system.MemoryStack;
import org.lwjgl.system.MemoryUtil;
public class GLFWUtil {
@NonNull
public static String glfwError(int error, long description) {
return switch(error) {
case GLFW.GLFW_NO_ERROR -> "GLFW_NO_ERROR";
case GLFW.GLFW_NOT_INITIALIZED -> "GLFW_NOT_INITIALIZED";
case GLFW.GLFW_NO_CURRENT_CONTEXT -> "GLFW_NO_CURRENT_CONTEXT";
case GLFW.GLFW_INVALID_ENUM -> "GLFW_INVALID_ENUM";
case GLFW.GLFW_INVALID_VALUE -> "GLFW_INVALID_VALUE";
case GLFW.GLFW_OUT_OF_MEMORY -> "GLFW_OUT_OF_MEMORY";
case GLFW.GLFW_API_UNAVAILABLE -> "GLFW_API_UNAVAILABLE";
case GLFW.GLFW_VERSION_UNAVAILABLE -> "GLFW_VERSION_UNAVAILABLE";
case GLFW.GLFW_PLATFORM_ERROR -> "GLFW_PLATFORM_ERROR";
case GLFW.GLFW_FORMAT_UNAVAILABLE -> "GLFW_FORMAT_UNAVAILABLE";
case GLFW.GLFW_NO_WINDOW_CONTEXT -> "GLFW_NO_WINDOW_CONTEXT";
case GLFW.GLFW_CURSOR_UNAVAILABLE -> "GLFW_CURSOR_UNAVAILABLE";
case GLFW.GLFW_FEATURE_UNAVAILABLE -> "GLFW_FEATURE_UNAVAILABLE";
case GLFW.GLFW_FEATURE_UNIMPLEMENTED -> "GLFW_FEATURE_UNIMPLEMENTED";
case GLFW.GLFW_PLATFORM_UNAVAILABLE -> "GLFW_PLATFORM_UNAVAILABLE";
default -> "UNKNOWN (" + error + ')';
} + (description != MemoryUtil.NULL ? ": " + MemoryUtil.memUTF8(description) : "");
}
@NonNull
public static String glfwError() {
try(MemoryStack stack = MemoryStack.stackPush()) {
PointerBuffer description = stack.callocPointer(1);
return glfwError(GLFW.glfwGetError(description), description.get(0));
}
}
}
@@ -0,0 +1,11 @@
package speiger.src.coreengine.input.window;
public interface IWindowListener {
public void onChanged(Window window, Reason reason);
public static enum Reason {
CHANGE,
CLOSING;
}
}
@@ -0,0 +1,73 @@
package speiger.src.coreengine.input.window;
import java.util.List;
import org.lwjgl.PointerBuffer;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.glfw.GLFWVidMode;
import speiger.src.collections.longs.maps.impl.hash.Long2ObjectLinkedOpenHashMap;
import speiger.src.collections.longs.maps.interfaces.Long2ObjectMap;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.lists.ObjectList;
public class Monitor
{
final long id;
ObjectList<VideoMode> modes = new ObjectArrayList<>();
VideoMode defaultMode;
int xOffset;
int yOffset;
public Monitor(long id) {
this.id = id;
GLFWVidMode.Buffer buffer = GLFW.glfwGetVideoModes(id);
for(int i = buffer.limit() - 1;i >= 0;--i) {
VideoMode videomode = new VideoMode(buffer.position(i));
if(videomode.redBits() >= 8 && videomode.greenBits() >= 8 && videomode.blueBits() >= 8)
modes.add(videomode);
}
defaultMode = new VideoMode(GLFW.glfwGetVideoMode(id));
int[] xPos = new int[1];
int[] yPos = new int[1];
GLFW.glfwGetMonitorPos(id, xPos, yPos);
xOffset = xPos[0];
yOffset = yPos[0];
}
//@formatter:off
public long id() { return id; }
public String name() { return GLFW.glfwGetMonitorName(id); }
public int size() { return modes.size(); }
public VideoMode getMode(int index) { return modes.get(index); }
public boolean has(VideoMode mode) { return modes.indexOf(mode) != -1; }
public List<VideoMode> videoModes() { return modes.unmodifiable(); }
public VideoMode defaultMode() { return defaultMode; }
public int xOffset() { return xOffset; }
public int yOffset() { return yOffset; }
//@formatter:on
public int getOverlap(int minX, int minY, int maxX, int maxY) {
int x = Math.max(0, Math.clamp(maxX, xOffset, xOffset + defaultMode.width()) - Math.clamp(minX, xOffset, xOffset + defaultMode.width()));
int y = Math.max(0, Math.clamp(maxY, yOffset, yOffset + defaultMode.height()) - Math.clamp(minY, yOffset, yOffset + defaultMode.height()));
return x * y;
}
@Override
public boolean equals(Object obj) { return obj instanceof Monitor mon && mon.id == id; }
@Override
public int hashCode() { return Long.hashCode(id); }
@Override
public String toString() { return "Monitor[Name=\""+name()+"\", Id="+id+", Width="+defaultMode.width()+", Height="+defaultMode.height()+", Modes="+size()+"]"; }
public static Long2ObjectMap<Monitor> createMonitors() {
Long2ObjectMap<Monitor> monitors = new Long2ObjectLinkedOpenHashMap<>();
PointerBuffer buffer = GLFW.glfwGetMonitors();
for(int i = 0,m=buffer.limit();i<m;i++) {
long id = buffer.get();
monitors.put(id, new Monitor(id));
}
return monitors;
}
}
@@ -0,0 +1,13 @@
package speiger.src.coreengine.input.window;
import org.lwjgl.glfw.GLFWVidMode;
public record VideoMode(int width, int height, int redBits, int greenBits, int blueBits, int refreshrate) {
public VideoMode(GLFWVidMode buffer) {
this(buffer.width(), buffer.height(), buffer.redBits(), buffer.greenBits(), buffer.blueBits(), buffer.refreshRate());
}
public VideoMode(GLFWVidMode.Buffer buffer) {
this(buffer.width(), buffer.height(), buffer.redBits(), buffer.greenBits(), buffer.blueBits(), buffer.refreshRate());
}
}
@@ -0,0 +1,349 @@
package speiger.src.coreengine.input.window;
import java.util.List;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.system.CallbackI;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.coreengine.graphics.api.core.Graphics;
import speiger.src.coreengine.graphics.api.core.GraphicsDevice;
import speiger.src.coreengine.graphics.api.core.GraphicsSurface;
import speiger.src.coreengine.input.window.IWindowListener.Reason;
import speiger.src.coreengine.input.window.WindowCallback.ReloadFunction;
import speiger.src.coreengine.math.BitUtil;
import speiger.src.coreengine.math.vector.ints.Vec2i;
import speiger.src.coreengine.math.vector.ints.Vec4i;
import speiger.src.coreengine.utils.collections.FlagHolder;
public class Window {
static final int VISIBLE = 1;
static final int VSYNC = 2;
static final int FOCUS = 4;
static final int CPU_FPS_CAP = 8;
static final int FULL_SCREEN = 16;
static final int MAXIMIZED = 32;
static final int BORDERLESS = 64;
static final int RESIZABLE = 128;
static final int FLOATING = 256;
static final int CLOSE = 512;
static final int WINDOW_CHANGE = 1024;
WindowManager manager;
GraphicsDevice device;
GraphicsSurface surface;
FlagHolder flags = new FlagHolder();
long id;
VideoMode fullScreenMode;
String title = "";
int x;
int y;
int width;
int height;
int frameWidth;
int frameHeight;
Vec4i[] backup = new Vec4i[] {Vec4i.mutable(), Vec4i.mutable()};
int backupIndex = 0;
final int antialiasing;
List<WindowCallback> callbacks = new ObjectArrayList<>();
List<IWindowListener> listeners = new ObjectArrayList<>();
protected Window(WindowBuilder builder, Graphics graphics) {
manager = builder.manager;
title = builder.title;
frameWidth = width = builder.width;
frameHeight = height = builder.height;
antialiasing = builder.antiAlis;
fullScreenMode = builder.fullScreenTarget;
flags.setFlag(BORDERLESS, builder.borderless);
flags.setFlag(FULL_SCREEN, builder.fullScreen);
flags.setFlag(FLOATING, builder.floating);
flags.setFlag(RESIZABLE, builder.resizable);
flags.setFlag(VSYNC, builder.vsync);
flags.setFlag(CPU_FPS_CAP, builder.fpsCap);
Monitor monitor = manager.getMonitor(builder.monitor);
if(monitor == null) throw new IllegalStateException("Monitor is missing: "+monitor);
boolean fullscreen = builder.fullScreen;
VideoMode mode = monitor.defaultMode();
id = manager.joinOnWindowThread(() -> {
graphics.setupWindowArguments();
for(int i = 0,m=builder.windowHints.size();i<m;i++) {
long value = builder.windowHints.getLong(i);
GLFW.glfwWindowHint(BitUtil.intKey(value), BitUtil.intValue(value));
}
GLFW.glfwWindowHint(GLFW.GLFW_SAMPLES, antialiasing);
GLFW.glfwWindowHint(GLFW.GLFW_RESIZABLE, flags.isFlagSet(RESIZABLE) ? GLFW.GLFW_TRUE : GLFW.GLFW_FALSE);
GLFW.glfwWindowHint(GLFW.GLFW_DECORATED, flags.isFlagNotSet(FULL_SCREEN) && flags.isFlagSet(BORDERLESS) ? GLFW.GLFW_FALSE : GLFW.GLFW_TRUE);
GLFW.glfwWindowHint(GLFW.GLFW_FLOATING, flags.isFlagNotSet(FULL_SCREEN) && flags.isFlagSet(FLOATING) ? GLFW.GLFW_TRUE : GLFW.GLFW_FALSE);
GLFW.glfwWindowHint(GLFW.GLFW_MAXIMIZED, flags.isFlagNotSet(FULL_SCREEN) && flags.isFlagSet(MAXIMIZED) ? GLFW.GLFW_TRUE : GLFW.GLFW_FALSE);
return GLFW.glfwCreateWindow(fullscreen ? mode.width() : width, fullscreen ? mode.height() : height, title, builder.fullScreen ? monitor.id() : 0, manager.getPrimaryWindow());
});
if(id == 0) throw new IllegalStateException("Window Couldn't be Created");
manager.addWindow(this);
manager.joinOnWindowThread(this::createWindowListeners);
GLFW.glfwMakeContextCurrent(id);
device = graphics.createDevice(this);
surface = device.createSurface();
x = monitor.xOffset() + (builder.center ? (mode.width() / 2) - (width / 2) : 0);
y = monitor.yOffset() + (builder.center ? (mode.height() / 2) - (height / 2) : 0);
if(!fullscreen) manager.joinOnWindowThread(() -> GLFW.glfwSetWindowPos(id, x, y));
GLFW.glfwSwapInterval(flags.isFlagSet(VSYNC) ? 1 : 0);
fetchWindowBounds();
updateViewport();
}
protected void createWindowListeners() {
addCallback(this::framebuffer, GLFW::glfwSetFramebufferSizeCallback);
addCallback(this::focused, GLFW::glfwSetWindowFocusCallback);
addCallback(this::position, GLFW::glfwSetWindowPosCallback);
addCallback(this::bounds, GLFW::glfwSetWindowSizeCallback);
}
private void framebuffer(long window, int width, int height) {
frameWidth = width;
frameHeight = height;
flags.setFlag(WINDOW_CHANGE);
}
private void focused(long window, boolean focused) {
manager.updateFocus(this, focused);
}
private void position(long window, int x, int y) {
this.x = x;
this.y = y;
flags.setFlag(WINDOW_CHANGE);
}
private void bounds(long window, int width, int height) {
this.width = width;
this.height = height;
flags.setFlag(WINDOW_CHANGE);
}
protected void fetchWindowBounds() {
Vec2i result = manager.joinOnWindowThread(() -> {
int[] width = new int[1];
int[] height = new int[1];
GLFW.glfwGetWindowSize(id, width, height);
return Vec2i.of(width[0], height[0]);
});
this.frameWidth = result.x();
this.frameHeight = result.y();
}
public void updateViewport() {
flags.clearFlag(WINDOW_CHANGE);
// GLStateTracker.instance().viewPort.setDefault(0, 0, frameWidth, frameHeight);
for(int i = 0,m=listeners.size();i<m;i++) {
listeners.get(i).onChanged(this, Reason.CHANGE);
}
}
public void setupContext() {
GLFW.glfwMakeContextCurrent(id);
}
public void beginFrame() {
setupContext();
if(flags.isFlagSet(WINDOW_CHANGE)) updateViewport();
}
public void handleInput() { manager.processDevices(id); }
public boolean shouldClose() { return flags.isFlagSet(CLOSE) || GLFW.glfwWindowShouldClose(id); }
public void finishFrame() { GLFW.glfwSwapBuffers(id); }
public void destroy() {
manager.removeWindow(id);
for(int i = 0,m=listeners.size();i<m;i++) {
listeners.get(i).onChanged(this, Reason.CLOSING);
}
manager.joinOnWindowThread(() -> {
GLFW.glfwDestroyWindow(id);
callbacks.forEach(WindowCallback::destroy);
callbacks.clear();
});
}
@SuppressWarnings("unchecked")
public <T extends CallbackI> WindowCallback addCallback(T listener, ReloadFunction<T> function) {
if(!manager.isManagerThread()) return manager.joinOnWindowThread(() -> addCallback(listener, function));
WindowCallback callback = new WindowCallback(listener, (ReloadFunction<CallbackI>)function);
callbacks.add(callback);
callback.load(id);
return callback;
}
public void removeCallback(WindowCallback callback) {
if(callbacks.remove(callback)) {
manager.joinOnWindowThread(callback::destroy);
}
}
public void addListener(IWindowListener listener) {
listeners.add(listener);
}
public void removeListener(IWindowListener listener) {
listeners.remove(listener);
}
public void title(String name) {
if(name == null || title.equals(name)) return;
title = name;
manager.joinOnWindowThread(() -> GLFW.glfwSetWindowTitle(id, title));
}
public void vsync(boolean vsync) {
if(!flags.setFlag(VSYNC, vsync)) return;
GLFW.glfwSwapInterval(vsync ? 1 : 0);
}
public void fpsCap(boolean fpsCap) {
flags.setFlag(CPU_FPS_CAP, fpsCap);
}
public void visible(boolean visible) {
if(!flags.setFlag(VISIBLE, visible)) return;
if(visible) manager.joinOnWindowThread(() -> GLFW.glfwShowWindow(id));
else manager.joinOnWindowThread(() -> GLFW.glfwHideWindow(id));
}
public void floating(boolean floating) {
if(flags.isFlagNotSet(FULL_SCREEN) && flags.setFlag(FLOATING, floating)) {
manager.joinOnWindowThread(() -> GLFW.glfwSetWindowAttrib(id, GLFW.GLFW_FLOATING, floating ? GLFW.GLFW_TRUE : GLFW.GLFW_FALSE));
}
}
public void maximized(boolean maximized) {
if(flags.isFlagNotSet(FULL_SCREEN) && flags.setFlag(MAXIMIZED, maximized)) {
if(maximized) {
backupSize();
manager.joinOnWindowThread(() -> GLFW.glfwMaximizeWindow(id));
}
else {
manager.joinOnWindowThread(() -> GLFW.glfwRestoreWindow(id));
restoreSize();
}
fetchWindowBounds();
}
}
public void resizeable(boolean resizeable) {
if(flags.isFlagNotSet(FULL_SCREEN) && flags.setFlag(RESIZABLE, resizeable)) {
manager.joinOnWindowThread(() -> GLFW.glfwSetWindowAttrib(id, GLFW.GLFW_RESIZABLE, resizeable ? GLFW.GLFW_TRUE : GLFW.GLFW_FALSE));
}
}
public void borderless(boolean borderless) {
if(flags.isFlagNotSet(FULL_SCREEN) && flags.setFlag(BORDERLESS, borderless)) {
manager.joinOnWindowThread(() -> GLFW.glfwSetWindowAttrib(id, GLFW.GLFW_DECORATED, borderless ? GLFW.GLFW_FALSE : GLFW.GLFW_TRUE));
if(flags.isFlagSet(MAXIMIZED)) {
manager.joinOnWindowThread(() -> {
GLFW.glfwRestoreWindow(id);
GLFW.glfwMaximizeWindow(id);
});
fetchWindowBounds();
}
}
}
public void fullscreen(boolean fullscreen) {
if(flags.setFlag(FULL_SCREEN, fullscreen)) {
if(fullscreen) {
Monitor monitor = manager.getMonitorForWindow(this);
if(monitor == null) {
flags.clearFlag(FULL_SCREEN);
return;
}
backupSize();
VideoMode mode = fullScreenMode == null || !monitor.has(fullScreenMode) ? monitor.defaultMode() : fullScreenMode;
x = 0;
y = 0;
width = mode.width();
height = mode.height();
manager.joinOnWindowThread(() -> GLFW.glfwSetWindowMonitor(id, monitor.id(), 0, 0, mode.width(), mode.height(), mode.refreshrate()));
}
else
{
restoreSize();
manager.joinOnWindowThread(() -> {
GLFW.glfwSetWindowMonitor(id, 0, x, y, width, height, -1);
if(flags.isFlagSet(BORDERLESS)) {
GLFW.glfwSetWindowAttrib(id, GLFW.GLFW_DECORATED, GLFW.GLFW_FALSE);
}
if(flags.isFlagSet(MAXIMIZED)) {
GLFW.glfwMaximizeWindow(id);
}
});
}
fetchWindowBounds();
updateViewport();
GLFW.glfwSwapInterval(flags.isFlagSet(VSYNC) ? 1 : 0);
}
}
protected void backupSize() {
if(backupIndex >= backup.length) return;
backup[backupIndex++].set(x, y, width, height);
}
protected void restoreSize() {
if(backupIndex == 0) return;
Vec4i prev = backup[--backupIndex];
x = prev.x();
y = prev.y();
width = prev.z();
height = prev.w();
}
public boolean position(int x, int y) {
if(flags.isAnyFlagSet(MAXIMIZED | FULL_SCREEN) || (this.x == x && this.y == y)) return false;
this.x = x;
this.y = y;
manager.joinOnWindowThread(() -> GLFW.glfwSetWindowPos(id, x, y));
flags.setFlag(WINDOW_CHANGE);
return true;
}
public boolean size(int width, int height) {
if(flags.isAnyFlagSet(MAXIMIZED | FULL_SCREEN) || (this.width == width && this.height == height)) return false;
this.width = width;
this.height = height;
manager.joinOnWindowThread(() -> GLFW.glfwSetWindowSize(id, width, height));
flags.setFlag(WINDOW_CHANGE);
return true;
}
public boolean width(int width) { return size(width, height); }
public boolean height(int height) { return size(width, height); }
public long id() { return id; }
public boolean isPrimaryWindow() { return manager.primaryWindow == this; }
public VideoMode desiredFullScreen() { return fullScreenMode; }
public WindowManager manager() { return manager; }
public GraphicsDevice device() { return device; }
public GraphicsSurface surface() { return surface; }
public int x() { return x; }
public int y() { return y; }
public int width() { return frameWidth; }
public int height() { return frameHeight; }
public int screenWidth() { return width; }
public int screenHeight() { return height; }
public boolean changed() { return flags.isFlagSet(WINDOW_CHANGE); }
public String title() { return title; }
public boolean isVsync() { return flags.isFlagSet(VSYNC); }
public boolean shouldFPSCap() { return flags.isFlagSet(CPU_FPS_CAP); }
public boolean isVisible() { return flags.isFlagSet(VISIBLE); }
public boolean isFloating() { return flags.isFlagSet(FLOATING); }
public boolean isMaximized() { return flags.isFlagSet(MAXIMIZED); }
public boolean isResizeable() { return flags.isFlagSet(RESIZABLE); }
public boolean isBorderless() { return flags.isFlagSet(BORDERLESS); }
public boolean isFullscreen() { return flags.isFlagSet(FULL_SCREEN); }
public int antialiasing() { return antialiasing; }
}
@@ -0,0 +1,124 @@
package speiger.src.coreengine.input.window;
import java.util.Objects;
import org.lwjgl.glfw.GLFW;
import speiger.src.collections.longs.lists.LongArrayList;
import speiger.src.collections.longs.lists.LongList;
import speiger.src.coreengine.math.BitUtil;
public class WindowBuilder {
WindowManager manager;
long monitor;
LongList windowHints = new LongArrayList();
VideoMode fullScreenTarget;
String title = "";
int width = 640;
int height = 480;
int antiAlis = 4;
boolean vsync = true;
boolean fpsCap;
boolean fullScreen;
boolean maximized;
boolean resizable = true;
boolean floating;
boolean borderless;
boolean center = true;
WindowBuilder(WindowManager manager) {
this.manager = manager;
monitor = GLFW.glfwGetPrimaryMonitor();
}
public WindowBuilder title(String title) {
this.title = Objects.requireNonNull(title);
return this;
}
public WindowBuilder width(int width) {
this.width = Math.max(1, width);
return this;
}
public WindowBuilder height(int height) {
this.height = Math.max(1, height);
return this;
}
public WindowBuilder antialis(int antiAlis) {
this.antiAlis = Math.max(1, antiAlis);
return this;
}
public WindowBuilder fullscreen(boolean fullScreen) {
this.fullScreen = fullScreen;
borderless &= !fullScreen;
floating &= !fullScreen;
maximized &= !fullScreen;
center &= !fullScreen;
return this;
}
public WindowBuilder maximized(boolean maximized) {
this.maximized = maximized;
this.fullScreen &= !maximized;
center &= !maximized;
return this;
}
public WindowBuilder borderless(boolean borderless) {
this.borderless = borderless;
fullScreen &= !borderless;
return this;
}
public WindowBuilder floating(boolean floating) {
this.floating = floating;
fullScreen &= !floating;
return this;
}
public WindowBuilder resizeable(boolean resizable) {
this.resizable = resizable;
return this;
}
public WindowBuilder centered(boolean center) {
this.center = center;
this.maximized &= !center;
this.fullScreen &= !center;
return this;
}
public WindowBuilder monitor(Monitor monitor) {
if(monitor == null || monitor.defaultMode() == null) return this;
this.monitor = monitor.id();
return this;
}
public WindowBuilder fullScreenMode(VideoMode mode) {
this.fullScreenTarget = mode;
return this;
}
public WindowBuilder vsync(boolean value) {
vsync = value;
return this;
}
public WindowBuilder fpsCap(boolean cap) {
fpsCap = cap;
return this;
}
public WindowBuilder addCustomHint(int key, int value) {
windowHints.add(BitUtil.toLong(key, value));
return this;
}
public Window build() {
return manager.create(this);
}
}
@@ -0,0 +1,40 @@
package speiger.src.coreengine.input.window;
import org.lwjgl.system.Callback;
import org.lwjgl.system.CallbackI;
public class WindowCallback {
ReloadFunction<CallbackI> function;
CallbackI listener;
Callback callback;
public WindowCallback(CallbackI listener, ReloadFunction<CallbackI> function) {
this.function = function;
this.listener = listener;
}
public void reload(long windowId) {
destroy();
load(windowId);
}
public void load(long windowId) {
callback = function.applyListener(windowId, listener);
}
public void destroy() {
if(callback == null) return;
callback.free();
callback = null;
}
public static interface ReloadFunction<T extends CallbackI> {
Callback applyListener(long window, T listener);
}
public static interface SimpleReloadFunction<T extends CallbackI> extends ReloadFunction<T> {
Callback applyListener(T listener);
@Override
default Callback applyListener(long window, T listener) { return applyListener(listener); }
}
}
@@ -0,0 +1,236 @@
package speiger.src.coreengine.input.window;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.system.Callback;
import org.lwjgl.system.CallbackI;
import org.lwjgl.system.Platform;
import speiger.src.collections.longs.maps.impl.concurrent.Long2ObjectConcurrentOpenHashMap;
import speiger.src.collections.longs.maps.interfaces.Long2ObjectMap;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.coreengine.graphics.api.core.Graphics;
import speiger.src.coreengine.input.device.InputDevice;
import speiger.src.coreengine.input.window.WindowCallback.SimpleReloadFunction;
public class WindowManager {
public static final ScopedValue<WindowManager> MANAGER = ScopedValue.newInstance();
BlockingQueue<Runnable> tasks = new LinkedBlockingQueue<>();
Graphics graphcis;
Long2ObjectMap<Window> windows = new Long2ObjectConcurrentOpenHashMap<>();
Long2ObjectMap<Monitor> monitors;
Window activeWindow;
Window primaryWindow;
List<WindowCallback> callbacks = new ObjectArrayList<>();
Callback monitorTracker;
List<InputDevice> devices = new ObjectArrayList<>();
boolean closing = false;
Thread manager;
public void setup(Runnable runnable, Graphics graphics) {
if(this.graphcis != null) throw new IllegalStateException("We already init the Graphis Pipeline");
this.graphcis = graphics;
int[] major = new int[1];
int[] minor = new int[1];
int[] revision = new int[1];
GLFW.glfwGetVersion(major, minor, revision);
System.out.println("GLFW Version ["+major[0]+"."+minor[0]+"."+revision[0]+"] found");
GLFW.glfwSetErrorCallback((E, D) -> System.out.println(GLFWUtil.glfwError(E, D)));
manager = Thread.currentThread();
manager.setName("Window Manager");
if(Platform.get() == Platform.LINUX) {
GLFW.glfwInitHint(GLFW.GLFW_PLATFORM, GLFW.glfwPlatformSupported(GLFW.GLFW_PLATFORM_WAYLAND) ? GLFW.GLFW_PLATFORM_WAYLAND : GLFW.GLFW_PLATFORM_X11);
}
if(!GLFW.glfwInit()) {
throw new IllegalStateException("GLFW Couldn't init. Reason: "+GLFWUtil.glfwError());
}
monitors = Monitor.createMonitors();
addCallback(this::onMonitorChanged, GLFW::glfwSetMonitorCallback);
Thread.ofPlatform().start(() -> ScopedValue.where(MANAGER, this).run(runnable));
while(!closing) {
Runnable task = null;
try { task = tasks.take(); }
catch(Exception e) { e.printStackTrace(); }
if(task == null) continue;
try { task.run(); }
catch(Exception e) { e.printStackTrace(); }
}
while(!tasks.isEmpty()) {
try { tasks.poll().run(); }
catch(Exception e) { e.printStackTrace(); }
}
callbacks.forEach(WindowCallback::destroy);
callbacks.clear();
System.out.println("GLFW Shutting down");
GLFW.glfwTerminate();
GLFW.glfwSetErrorCallback(null).free();
System.exit(0);
}
@Nullable
public static WindowManager get() {
return MANAGER.orElse(null);
}
public boolean isManagerThread() {
return manager.threadId() == Thread.currentThread().threadId();
}
public void joinOnWindowThread(Runnable run) {
CompletableFuture.runAsync(run, tasks::add).join();
}
public <T> T joinOnWindowThread(Supplier<T> provider) {
return CompletableFuture.supplyAsync(provider, tasks::add).join();
}
public CompletableFuture<Void> runOnWindowThread(Runnable run) {
return CompletableFuture.runAsync(run, tasks::add);
}
public <T> CompletableFuture<T> runOnWindowThread(Supplier<T> provider) {
return CompletableFuture.supplyAsync(provider, tasks::add);
}
public void shutdown() {
closing = true;
tasks.add(() -> {});
}
private void onMonitorChanged(long monitor, int event) {
switch(event) {
case GLFW.GLFW_CONNECTED -> monitors.put(monitor, new Monitor(monitor));
case GLFW.GLFW_DISCONNECTED -> monitors.remove(monitor);
}
}
public WindowBuilder builder() { return new WindowBuilder(this); }
Window create(WindowBuilder builder) { return new Window(builder, graphcis); }
public void addDevices(InputDevice...devices) {
for(InputDevice device : devices) addDevice(device);
}
public void addDevice(InputDevice device) {
devices.add(device);
windows.values().forEach(device::register);
}
public void removeDevice(InputDevice device) { devices.remove(device); }
public void processDevices(long windowId) {
for(int i = 0,m=devices.size();i<m;i++) {
devices.get(i).processInput(windowId);
}
}
@SuppressWarnings("unchecked")
public <T extends CallbackI> WindowCallback addCallback(T listener, SimpleReloadFunction<T> function) {
WindowCallback callback = new WindowCallback(listener, (SimpleReloadFunction<CallbackI>)function);
callbacks.add(callback);
callback.load(0L);
return callback;
}
public void removeCallback(WindowCallback callback) {
if(callbacks.remove(callback)) {
callback.destroy();
}
}
void addWindow(Window window) {
windows.put(window.id(), window);
if(primaryWindow == null) primaryWindow = window;
for(int i = 0,m=devices.size();i<m;i++) {
devices.get(i).register(window);
}
}
void updateWindow(long oldId) {
Window prev = windows.remove(oldId);
if(prev == null) return;
windows.put(prev.id(), prev);
}
void removeWindow(long id) {
Window window = windows.remove(id);
if(window == null) return;
if(window == activeWindow) activeWindow = null;
if(window == primaryWindow) {
Iterator<Window> iter = windows.values().iterator();
primaryWindow = iter.hasNext() ? iter.next() : null;
}
}
void updateFocus(Window window, boolean focus) {
if(focus) this.activeWindow = window;
else if(activeWindow == window) activeWindow = null;
}
void updateInputs(long windowId) {
for(int i = 0,m=devices.size();i<m;i++) {
InputDevice device = devices.get(i);
device.reset(windowId);
device.processInput(windowId);
}
}
public Graphics graphics() {
return graphcis;
}
public long getActiveWindow() {
return activeWindow == null ? 0L : activeWindow.id();
}
public long getPrimaryWindow() {
return primaryWindow == null ? 0 : primaryWindow.id();
}
public Window getWindow(long window) {
return windows.get(window);
}
public Monitor getMonitor(long id) {
return monitors.get(id);
}
public Monitor getPriamryMonitor() {
return getMonitor(GLFW.glfwGetPrimaryMonitor());
}
public Monitor getMonitorForWindow(Window window) {
long current = GLFW.glfwGetWindowMonitor(window.id());
if(current != 0L) return getMonitor(current);
int minX = window.x();
int minY = window.y();
int maxX = minX + window.screenWidth();
int maxY = minY + window.screenHeight();
int largest = 0;
Monitor mon = null;
for(Monitor monitor : monitors.values()) {
int next = monitor.getOverlap(minX, minY, maxX, maxY);
if(next > largest) {
largest = next;
mon = monitor;
}
}
return mon;
}
}
@@ -70,14 +70,14 @@ public class NewInputTest {
public void run() {
Configuration.HARFBUZZ_LIBRARY_NAME.set(FreeType.getLibrary());
GLFW.glfwInit();
manager.initialize();
manager.initialize(null);
Mouse.INSTANCE.init(bus);
Keyboard.INSTANCE.init(bus);
Joystick.INSTANCE.init(manager, bus);
FileDrop.INSTANCE.init(bus);
manager.addDevices(Mouse.INSTANCE, Keyboard.INSTANCE, Joystick.INSTANCE, FileDrop.INSTANCE);
Window window = manager.builder().title("Testing Engine").width(800).height(600).antialis(0).build(null);
Window secondWindow = manager.builder().title("Second Window Engine").width(800).height(600).antialis(0).build(null);
Window window = manager.builder().title("Testing Engine").width(800).height(600).antialis(0).build();
Window secondWindow = manager.builder().title("Second Window Engine").width(800).height(600).antialis(0).build();
Thread.ofPlatform().start(() -> drawSecondScreen(secondWindow));
window.setupContext();
shaderTest.register();
@@ -3,7 +3,6 @@ package speiger.src.coreengine;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import org.lwjgl.glfw.GLFW;
import org.lwjgl.opengl.GL11;
import org.lwjgl.system.Configuration;
import org.lwjgl.util.freetype.FreeType;
@@ -14,7 +13,6 @@ import speiger.src.coreengine.assets.manager.AssetManager;
import speiger.src.coreengine.graphics.api.buffer.VertexBuffer;
import speiger.src.coreengine.graphics.api.buffer.states.BufferState;
import speiger.src.coreengine.graphics.api.buffer.states.BufferType;
import speiger.src.coreengine.graphics.api.core.Graphics;
import speiger.src.coreengine.graphics.api.core.GraphicsCommandBuffer;
import speiger.src.coreengine.graphics.api.core.GraphicsDevice;
import speiger.src.coreengine.graphics.api.core.GraphicsSurface;
@@ -34,13 +32,13 @@ import speiger.src.coreengine.graphics.api.utils.ExecutionType;
import speiger.src.coreengine.graphics.api.vertex.VertexLayout;
import speiger.src.coreengine.graphics.api.vertex.VertexLayout.Usage;
import speiger.src.coreengine.graphics.opengl.core.GLGraphics;
import speiger.src.coreengine.input.device.FileDrop;
import speiger.src.coreengine.input.device.Joystick;
import speiger.src.coreengine.input.device.Keyboard;
import speiger.src.coreengine.input.device.Mouse;
import speiger.src.coreengine.input.window.Window;
import speiger.src.coreengine.input.window.WindowManager;
import speiger.src.coreengine.math.vector.matrix.Matrix4f;
import speiger.src.coreengine.rendering.input.devices.FileDrop;
import speiger.src.coreengine.rendering.input.devices.Joystick;
import speiger.src.coreengine.rendering.input.devices.Keyboard;
import speiger.src.coreengine.rendering.input.devices.Mouse;
import speiger.src.coreengine.rendering.input.window.Window;
import speiger.src.coreengine.rendering.input.window.WindowManager;
import speiger.src.coreengine.rendering.tesselation.buffer.VertexBuilder;
import speiger.src.coreengine.rendering.tesselation.format.VertexTypes;
import speiger.src.coreengine.rendering.textures.custom.Drawable;
@@ -54,24 +52,20 @@ public class NewRenderEngineTest {
AssetManager assets = AssetManager.single(IAssetPackage.asset(IOUtils.getBaseLocation()));
public static void main(String... args) {
new NewRenderEngineTest().run();
Configuration.HARFBUZZ_LIBRARY_NAME.set(FreeType.getLibrary());
NewRenderEngineTest test = new NewRenderEngineTest();
test.manager.setup(test::run, new GLGraphics(test.assets));
}
public void run() {
Configuration.HARFBUZZ_LIBRARY_NAME.set(FreeType.getLibrary());
GLFW.glfwInit();
manager.initialize();
Mouse.INSTANCE.init(bus);
Keyboard.INSTANCE.init(bus);
Joystick.INSTANCE.init(manager, bus);
FileDrop.INSTANCE.init(bus);
manager.addDevices(Mouse.INSTANCE, Keyboard.INSTANCE, Joystick.INSTANCE, FileDrop.INSTANCE);
Graphics graphics = new GLGraphics(assets);
Window window = manager.builder().title("Testing Engine").width(800).height(600).antialis(0).build(graphics);
GraphicsDevice device = graphics.createDevice(window);
window.setupContext();
GraphicsSurface surface = device.createSurface();
Window window = manager.builder().title("Testing Engine").width(800).height(600).antialis(0).build();
Thread.currentThread().setName("Main Thread");
GraphicsDevice device = window.device();
int size = 512;
int half = size >> 1;
int base = size >> 3;
@@ -84,15 +78,6 @@ public class NewRenderEngineTest {
VertexBuilder builder = new VertexBuilder(255);
builder.start(null, VertexTypes.TESTING);
// builder.pos(350F, 250F, 0).tex(0F, 1F).rgba(-1).endVertex(); // Unten Links
// builder.pos(450F, 350F, 0).tex(1F, 0F).rgba(-1).endVertex(); // Oben Rechts
// builder.pos(450F, 250F, 0).tex(1F, 1F).rgba(-1).endVertex(); // Unten Rechts
//
// builder.pos(450F, 350F, 0).tex(1F, 0F).rgba(-1).endVertex(); // Oben Rechts
// builder.pos(350F, 250F, 0).tex(0F, 1F).rgba(-1).endVertex(); // Unten Links
// builder.pos(350F, 350F, 0).tex(0F, 0F).rgba(-1).endVertex(); // Oben Links
builder.pos(-0.5F, -0.5F, 0).tex(0F, 1F).rgba(-1).endVertex();
builder.pos(0.5F, -0.5F, 0).tex(1F, 1F).rgba(-1).endVertex();
builder.pos(0.5F, 0.5F, 0).tex(1F, 0F).rgba(-1).endVertex();
@@ -126,16 +111,22 @@ public class NewRenderEngineTest {
.withStage(ShaderType.FRAGMENT, ID.of("shader/newTestGui/fragment.fs"))
.withTexture("texture", 0, 0, TextureType.TEXTURE_2D)
.withUniform("Camera", 0, 0, 64)
.withFormat()
.attribute("in_position", 0, 3)
.attribute("in_tex", 1, 2)
.attribute("in_color", 2, 4, GraphicsDataType.UNSIGNED_BYTE, true)
.endFormat()
.withFormat(layout)
.build();
window.visible(true);
GraphicsCommandBuffer queue = device.createCommandBuffer(ExecutionType.IMMEDIATE);
GL11.glViewport(0, 0, window.width(), window.height());
GraphicsCommandBuffer queue = device.createCommandBuffer(ExecutionType.RECORDED);
queue.begin()
.pipeline(pipeline)
.mesh(mesh)
.texture(0, texture, sampler)
.uniform(0, buffer)
.drawArrays(0, 6)
.end();
GL11.glViewport(0, 0, window.width(), window.height());
GraphicsSurface surface = window.surface();
while(!window.shouldClose()) {
surface.beginFrame();
if(window.changed()) {
@@ -144,24 +135,19 @@ public class NewRenderEngineTest {
new Matrix4f().ortho(0, 0, window.width() >> scale, window.height() >> scale, 1000, -1000).store(mat);
buffer.bind().set(mat.flip().array()).unbind();
}
queue.begin();
queue.pipeline(pipeline)
.mesh(mesh)
.texture(0, texture, sampler)
.uniform(0, buffer)
.drawArrays(0, 6)
.drawArrays(0, 6);
queue.end();
device.queue().submitCommands(queue);
device.queue().submitFrame();
surface.finishFrame();
try {
Thread.sleep(1);
Thread.sleep(10);
}
catch(Exception e) {
e.printStackTrace();
}
}
window.destroy();
manager.shutdown();
}
}
@@ -9,12 +9,13 @@ import org.lwjgl.system.CallbackI;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.coreengine.graphics.api.core.Graphics;
import speiger.src.coreengine.graphics.api.core.GraphicsDevice;
import speiger.src.coreengine.graphics.api.core.GraphicsSurface;
import speiger.src.coreengine.math.BitUtil;
import speiger.src.coreengine.math.vector.ints.Vec4i;
import speiger.src.coreengine.rendering.input.window.IWindowListener.Reason;
import speiger.src.coreengine.rendering.input.window.WindowCallback.ReloadFunction;
import speiger.src.coreengine.rendering.input.window.WindowManager.WindowBuilder;
import speiger.src.coreengine.rendering.utils.GLStateTracker;
import speiger.src.coreengine.utils.collections.FlagHolder;
public class Window {
@@ -30,6 +31,8 @@ public class Window {
static final int CLOSE = 512;
static final int WINDOW_CHANGE = 1024;
WindowManager manager;
GraphicsDevice device;
GraphicsSurface surface;
FlagHolder flags = new FlagHolder();
long id;
VideoMode fullScreenMode;
@@ -63,8 +66,7 @@ public class Window {
flags.setFlag(RESIZABLE, builder.resizable);
flags.setFlag(VSYNC, builder.vsync);
flags.setFlag(CPU_FPS_CAP, builder.fpsCap);
if(graphics != null) graphics.setupWindowArguments();
else createDefaultWindowHints();
graphics.setupWindowArguments();
for(int i = 0,m=builder.windowHints.size();i<m;i++) {
long value = builder.windowHints.getLong(i);
GLFW.glfwWindowHint(BitUtil.intKey(value), BitUtil.intValue(value));
@@ -83,7 +85,7 @@ public class Window {
manager.addWindow(this);
createWindowListeners();
GLFW.glfwMakeContextCurrent(id);
if(graphics == null) capabilities = GL.createCapabilities(true);
// device = graphics.createDevice(this);
x = monitor.xOffset() + (builder.center ? (mode.width() / 2) - (width / 2) : 0);
y = monitor.yOffset() + (builder.center ? (mode.height() / 2) - (height / 2) : 0);
if(!fullscreen) GLFW.glfwSetWindowPos(id, x, y);
@@ -92,15 +94,6 @@ public class Window {
updateViewport();
}
protected void createDefaultWindowHints() {
GLFW.glfwDefaultWindowHints();
GLFW.glfwWindowHint(GLFW.GLFW_VISIBLE, GLFW.GLFW_FALSE);
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MAJOR, 4);
GLFW.glfwWindowHint(GLFW.GLFW_CONTEXT_VERSION_MINOR, 0);
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_PROFILE, GLFW.GLFW_OPENGL_CORE_PROFILE);
GLFW.glfwWindowHint(GLFW.GLFW_OPENGL_FORWARD_COMPAT, GLFW.GLFW_TRUE);
}
protected void createWindowListeners() {
addCallback(this::framebuffer, GLFW::glfwSetFramebufferSizeCallback);
addCallback(this::focused, GLFW::glfwSetWindowFocusCallback);
@@ -149,7 +142,6 @@ public class Window {
public void setupContext() {
if(capabilities != null) GL.setCapabilities(capabilities);
GLFW.glfwMakeContextCurrent(id);
}
public void beginFrame() {
@@ -19,6 +19,7 @@ import speiger.src.coreengine.rendering.input.devices.InputDevice;
import speiger.src.coreengine.rendering.input.window.WindowCallback.SimpleReloadFunction;
public class WindowManager {
Graphics graphcis;
Long2ObjectMap<Monitor> monitors;
Long2ObjectMap<Window> windows = new Long2ObjectConcurrentOpenHashMap<>();
Window activeWindow;
@@ -27,7 +28,8 @@ public class WindowManager {
Callback monitorTracker;
List<InputDevice> devices = new ObjectArrayList<>();
public void initialize() {
public void initialize(Graphics graphics) {
this.graphcis = graphics;
monitors = Monitor.createMonitors();
addCallback(this::onMonitorChanged, GLFW::glfwSetMonitorCallback);
}
@@ -40,7 +42,7 @@ public class WindowManager {
}
public WindowBuilder builder() { return new WindowBuilder(this); }
private Window create(WindowBuilder builder, Graphics graphics) { return new Window(builder, graphics); }
private Window create(WindowBuilder builder) { return new Window(builder, graphcis); }
public void addDevices(InputDevice...devices) {
for(InputDevice device : devices) addDevice(device);
@@ -259,8 +261,8 @@ public class WindowManager {
return this;
}
public Window build(Graphics graphics) {
return manager.create(this, graphics);
public Window build() {
return manager.create(this);
}
}
@@ -1,129 +1,129 @@
package speiger.src.coreengine.utils.eventbus;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.maps.impl.concurrent.Object2ObjectConcurrentOpenHashMap;
import speiger.src.collections.objects.maps.impl.hash.Object2ObjectOpenHashMap;
import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap;
import speiger.src.collections.objects.utils.ObjectLists;
public class EventBus
{
private static final Lookup LOOKUP = MethodHandles.lookup();
Map<Class<? extends Event>, Listeners> listeners = new Object2ObjectOpenHashMap<Class<? extends Event>, Listeners>().synchronize();
Object2ObjectMap<Object, List<EventListener>> instances = new Object2ObjectConcurrentOpenHashMap<>();
public <T extends Event> void register(Class<T> event, Consumer<T> listener) {
register(event, EventPriority.MEDIUM, listener);
}
public <T extends Event> void register(Class<T> event, EventPriority priority, Consumer<T> listener) {
if(instances.containsKey(listener)) return;
getListeners(event).addListener(priority, cast(listener));
instances.put(listener, ObjectLists.singleton(new EventListener(event, cast(listener))));
}
public void register(Object obj) {
register(obj, false);
}
public void register(Object obj, boolean superClasses) {
if(instances.containsKey(obj)) return;
final List<EventListener> list = new ObjectArrayList<>();
try {
register(obj.getClass().getDeclaredAnnotation(SubscribeEvent.class), obj, list);
find(obj.getClass(), superClasses, Class::getDeclaredMethods, t -> {
try {
t.setAccessible(true);
SubscribeEvent data = t.getAnnotation(SubscribeEvent.class);
if(data == null) return;
Consumer<Event> listener = new MethodListener(LOOKUP.unreflect(t).bindTo(obj));
Class<Event> clz = castClass(t.getParameterTypes()[0]);
getListeners(clz).addListener(data.priority(), listener);
list.add(new EventListener(clz, listener));
}
catch(Exception e) { e.printStackTrace(); }
});
find(obj.getClass(), superClasses, Class::getDeclaredFields, t -> {
try {
t.setAccessible(true);
register(t.getAnnotation(SubscribeEvent.class), t.get(obj), list);
}
catch(Exception e) { e.printStackTrace(); }
});
}
catch(Exception e) { e.printStackTrace(); }
if(list.isEmpty()) return;
instances.put(obj, list);
}
private void register(SubscribeEvent data, Object obj, List<EventListener> listeners) {
if(data == null || !(obj instanceof Consumer)) return;
getListeners(data.value()).addListener(data.priority(), cast(obj));
listeners.add(new EventListener(data.value(), cast(obj)));
}
private <T> void find(Class<?> clz, boolean superClasses, Function<Class<?>, T[]> mapper, Consumer<T> result) {
do {
for(T value : mapper.apply(clz)) result.accept(value);
clz = clz.getSuperclass();
}
while(clz != Object.class && superClasses);
}
public void unregister(Object obj) {
for(EventListener entry : instances.remOrDefault(obj, ObjectLists.empty())) {
getListeners(entry.event()).removeListeners(entry.listener());
}
}
public void post(Event event) {
Consumer<Event>[] listeners = getListeners(event.getClass()).getListeners();
if(listeners.length <= 0) return;
int index = 0;
try {
for(;index<listeners.length && !event.isCanceled();index++) {
listeners[index].accept(event);
}
}
catch(Exception e) {
e.printStackTrace();
}
}
protected Listeners getListeners(Class<? extends Event> event) {
Listeners result = listeners.get(event);
if(result == null) {
result = event == Event.class ? new Listeners() : new Listeners(getListeners(castClass(event.getSuperclass())));
listeners.put(event, result);
}
return result;
}
@SuppressWarnings("unchecked")
private <T> Consumer<T> cast(Object obj) {
return (Consumer<T>) obj;
}
@SuppressWarnings("unchecked")
private <T> Class<T> castClass(Class<?> clz) {
return (Class<T>) clz;
}
public static record EventListener(Class<? extends Event> event, Consumer<Event> listener) {}
public static record MethodListener(MethodHandle handle) implements Consumer<Event> {
@Override
public void accept(Event t) {
try { handle.invoke(t); }
catch(Throwable e) { e.printStackTrace(); }
}
}
}
package speiger.src.coreengine.utils.eventbus;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import speiger.src.collections.objects.lists.ObjectArrayList;
import speiger.src.collections.objects.maps.impl.concurrent.Object2ObjectConcurrentOpenHashMap;
import speiger.src.collections.objects.maps.impl.hash.Object2ObjectOpenHashMap;
import speiger.src.collections.objects.maps.interfaces.Object2ObjectMap;
import speiger.src.collections.objects.utils.ObjectLists;
public class EventBus
{
private static final Lookup LOOKUP = MethodHandles.lookup();
Map<Class<? extends Event>, Listeners> listeners = new Object2ObjectOpenHashMap<Class<? extends Event>, Listeners>().synchronize();
Object2ObjectMap<Object, List<EventListener>> instances = new Object2ObjectConcurrentOpenHashMap<>();
public <T extends Event> void register(Class<T> event, Consumer<T> listener) {
register(event, EventPriority.MEDIUM, listener);
}
public <T extends Event> void register(Class<T> event, EventPriority priority, Consumer<T> listener) {
if(instances.containsKey(listener)) return;
getListeners(event).addListener(priority, cast(listener));
instances.put(listener, ObjectLists.singleton(new EventListener(event, cast(listener))));
}
public void register(Object obj) {
register(obj, false);
}
public void register(Object obj, boolean superClasses) {
if(instances.containsKey(obj)) return;
final List<EventListener> list = new ObjectArrayList<>();
try {
register(obj.getClass().getDeclaredAnnotation(SubscribeEvent.class), obj, list);
find(obj.getClass(), superClasses, Class::getDeclaredMethods, t -> {
try {
t.setAccessible(true);
SubscribeEvent data = t.getAnnotation(SubscribeEvent.class);
if(data == null) return;
Consumer<Event> listener = new MethodListener(LOOKUP.unreflect(t).bindTo(obj));
Class<Event> clz = castClass(t.getParameterTypes()[0]);
getListeners(clz).addListener(data.priority(), listener);
list.add(new EventListener(clz, listener));
}
catch(Exception e) { e.printStackTrace(); }
});
find(obj.getClass(), superClasses, Class::getDeclaredFields, t -> {
try {
t.setAccessible(true);
register(t.getAnnotation(SubscribeEvent.class), t.get(obj), list);
}
catch(Exception e) { e.printStackTrace(); }
});
}
catch(Exception e) { e.printStackTrace(); }
if(list.isEmpty()) return;
instances.put(obj, list);
}
private void register(SubscribeEvent data, Object obj, List<EventListener> listeners) {
if(data == null || !(obj instanceof Consumer)) return;
getListeners(data.value()).addListener(data.priority(), cast(obj));
listeners.add(new EventListener(data.value(), cast(obj)));
}
private <T> void find(Class<?> clz, boolean superClasses, Function<Class<?>, T[]> mapper, Consumer<T> result) {
do {
for(T value : mapper.apply(clz)) result.accept(value);
clz = clz.getSuperclass();
}
while(clz != Object.class && superClasses);
}
public void unregister(Object obj) {
for(EventListener entry : instances.remOrDefault(obj, ObjectLists.empty())) {
getListeners(entry.event()).removeListeners(entry.listener());
}
}
public void post(Event event) {
Consumer<Event>[] listeners = getListeners(event.getClass()).getListeners();
if(listeners.length <= 0) return;
int index = 0;
try {
for(;index<listeners.length && !event.isCanceled();index++) {
listeners[index].accept(event);
}
}
catch(Exception e) {
e.printStackTrace();
}
}
protected Listeners getListeners(Class<? extends Event> event) {
Listeners result = listeners.get(event);
if(result == null) {
result = event == Event.class ? new Listeners() : new Listeners(getListeners(castClass(event.getSuperclass())));
listeners.put(event, result);
}
return result;
}
@SuppressWarnings("unchecked")
private <T> Consumer<T> cast(Object obj) {
return (Consumer<T>) obj;
}
@SuppressWarnings("unchecked")
private <T> Class<T> castClass(Class<?> clz) {
return (Class<T>) clz;
}
public static record EventListener(Class<? extends Event> event, Consumer<Event> listener) {}
public static record MethodListener(MethodHandle handle) implements Consumer<Event> {
@Override
public void accept(Event t) {
try { handle.invoke(t); }
catch(Throwable e) { e.printStackTrace(); }
}
}
}
@@ -0,0 +1,147 @@
package speiger.src.coreengine.math.color;
import speiger.src.coreengine.math.MathUtils;
public enum ColorSpaces {
ARGB {
@Override
public int color(int r, int g, int b, int a) { return (a & 0xFF) << 24 | (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF); }
@Override
public int r(int color) { return color >> 16 & 0xFF; }
@Override
public int g(int color) { return color >> 8 & 0xFF; }
@Override
public int b(int color) { return color & 0xFF; }
@Override
public int a(int color) { return color >> 24; }
@Override
public int setR(int color, int r) { return color & ~ColorUtils.R | ((r & 0xFF) << 16); }
@Override
public int setG(int color, int g) { return color & ~ColorUtils.G | ((g & 0xFF) << 8); }
@Override
public int setB(int color, int b) { return color & ~ColorUtils.B | (b & 0xFF); }
@Override
public int setA(int color, int a) { return color & ~ColorUtils.A | ((a & 0xFF) << 24); }
@Override
public int fromARGB(int color) { return color; }
@Override
public int toARGB(int color) { return color; }
},
ABGR {
@Override
public int color(int r, int g, int b, int a) { return (a & 0xFF) << 24 | (b & 0xFF) << 16 | (g & 0xFF) << 8 | (r & 0xFF); }
@Override
public int r(int color) { return color & 0xFF; }
@Override
public int g(int color) { return color >> 8 & 0xFF; }
@Override
public int b(int color) { return color >> 16 & 0xFF; }
@Override
public int a(int color) { return color >> 24 & 0xFF; }
@Override
public int setR(int color, int r) { return color & ~ColorUtils.B | (r & 0xFF); }
@Override
public int setG(int color, int g) { return color & ~ColorUtils.G | ((g & 0xFF) << 8); }
@Override
public int setB(int color, int b) { return color & ~ColorUtils.R | ((b & 0xFF) << 16); }
@Override
public int setA(int color, int a) { return color & ~ColorUtils.A | ((a & 0xFF) << 24); }
@Override
public int fromARGB(int color) { return color & 0xFF00FF00 | (color & 0xFF0000) >> 16 | (color & 0xFF) << 16; }
@Override
public int toARGB(int color) { return color & 0xFF00FF00 | (color & 0xFF0000) >> 16 | (color & 0xFF) << 16; }
};
public int color(float r, float g, float b) { return color((int)(r * 255F + 0.5F), (int)(g * 255F + 0.5F), (int)(b * 255F + 0.5F)); }
public int color(float r, float g, float b, float a) { return color((int)(r * 255F + 0.5F), (int)(g * 255F + 0.5F), (int)(b * 255F + 0.5F), (int)(a * 255F + 0.5F));}
public int color(int r, int g, int b) { return color(r, g, b, 255); }
public abstract int color(int r, int g, int b, int a);
public abstract int r(int color);
public abstract int g(int color);
public abstract int b(int color);
public abstract int a(int color);
public float rf(int color) { return r(color) / 255F; }
public float gf(int color) { return g(color) / 255F; }
public float bf(int color) { return b(color) / 255F; }
public float af(int color) { return a(color) / 255F; }
public abstract int setR(int color, int r);
public abstract int setG(int color, int g);
public abstract int setB(int color, int b);
public abstract int setA(int color, int a);
public int setRF(int color, float r) { return setR(color, (int)(r * 255F + 0.5F)); }
public int setGF(int color, float g) { return setG(color, (int)(g * 255F + 0.5F)); }
public int setBF(int color, float b) { return setB(color, (int)(b * 255F + 0.5F)); }
public int setAF(int color, float a) { return setA(color, (int)(a * 255F + 0.5F)); }
public abstract int toARGB(int color);
public abstract int fromARGB(int color);
public int darker(int color) { return darker(color, 0.7F); }
public int darker(int color, float factor) {
int r = Math.max(0, (int)(r(color) * factor));
int g = Math.max(0, (int)(g(color) * factor));
int b = Math.max(0, (int)(b(color) * factor));
return (a(color) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF);
}
public int brighter(int color) { return brighter(color, 0.7F); }
public int brighter(int color, float factor) {
int r = r(color);
int g = g(color);
int b = b(color);
int i = (int)(1.0 / (1.0 - factor));
if(r == 0 && g == 0 && b == 0) { return color(i & 0xFF, i & 0xFF, i & 0xFF, a(color)); }
if(r > 0 && r < i) r = i;
if(g > 0 && g < i) g = i;
if(b > 0 && b < i) b = i;
return color(Math.min(255, (int)(r / factor)), Math.min(255, (int)(g / factor)), Math.min(255, (int)(b / factor)), a(color) << 24);
}
public int fromHue(float hue, float saturation, float brightness) {
if(saturation == 0) {
int result = (int)(brightness * 255F + 0.5F);
return color(result, result, result);
}
float h = (hue - MathUtils.floor(hue)) * 6F;
float f = h - MathUtils.floor(h);
float p = brightness * (1F - saturation);
float q = brightness * (1F - saturation * f);
float t = brightness * (1F - (saturation * (1F - f)));
switch((int)h) {
case 0: return color(brightness, t, p);
case 1: return color(q, brightness, p);
case 2: return color(p, brightness, t);
case 3: return color(p, q, brightness);
case 4: return color(t, p, brightness);
case 5: return color(brightness, p, q);
default: return color(0, 0, 0);
}
}
public float[] toHue(int color) {
int r = r(color);
int g = g(color);
int b = b(color);
int cmax = (r > g) ? r : g;
if(b > cmax) cmax = b;
int cmin = (r < g) ? r : g;
if(b < cmin) cmin = b;
float length = cmax - cmin;
float[] result = new float[3];
result[1] = cmax == 0 ? 0F : length / cmax;
result[2] = cmax * ColorUtils.DEVIDER;
float hue = 0F;
if(result[1] != 0F) {
float redc = (cmax - r) / length;
float greenc = (cmax - g) / length;
float bluec = (cmax - b) / length;
if(r == cmax) hue = bluec - greenc;
else if(g == cmax) hue = 2F + redc - bluec;
else hue = 4F + greenc - redc;
hue /= 6F;
if(hue < 0) hue += 1F;
}
result[0] = hue;
return result;
}
}
@@ -0,0 +1,125 @@
package speiger.src.coreengine.math.color;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import speiger.src.collections.floats.lists.FloatList;
public class ColorUtils {
static final float DEVIDER = 1F / 255F;
public static final int R = 0xFF << 16;
public static final int G = 0xFF << 8;
public static final int B = 0xFF;
public static final int A = 0xFF << 24;
public static final long SIGN = 0x00000000FFFFFFFFL;
static final int ALL = 0xFFFFFFFF;
public static final int WHITE = rgb(255, 255, 255);
public static final int LIGHT_GRAY = rgb(192, 192, 192);
public static final int GRAY = rgb(128, 128, 128);
public static final int DARK_GRAY = rgb(64, 64, 64);
public static final int BLACK = rgb(0, 0, 0);
public static final int RED = rgb(255, 0, 0);
public static final int PINK = rgb(255, 175, 175);
public static final int PURPLE = rgb(106, 13, 173);
public static final int ORANGE = rgb(255, 200, 0);
public static final int YELLOW = rgb(255, 255, 0);
public static final int GREEN = rgb(0, 255, 0);
public static final int DARK_GREEN = rgb(7, 161, 0);
public static final int MAGENTA = rgb(255, 0, 255);
public static final int CYAN = rgb(0, 255, 255);
public static final int BLUE = rgb(0, 0, 255);
public static final int LIGHT_BLUE = rgb(0, 150, 255);
// Specialized Components that get reused
public static final int INVISIBLE = rgb(0, 0, 0, 0);
public static final int TEXT_DEFAULT_BACKGROUND = rgb(80, 80, 80, 144);
public static final int WINDOW_DEFAULT_BACKGROUND = rgb(64, 64, 64, 128);
public static final int POPUP_DEFAULT_BACKGROUND = rgb(85, 85, 85);
public static final int DESTRUCTION = rgb(255, 0, 0, 128);
public static byte[] toByteArray(int color, boolean alpha) {
byte[] data = new byte[alpha ? 4 : 3];
data[0] = (byte)((color >> 16) & 0xFF);
data[1] = (byte)((color >> 8) & 0xFF);
data[2] = (byte)(color & 0xFF);
if(alpha) data[3] = (byte)((color >> 24) & 0xFF);
return data;
}
public static void write(int color, boolean alpha, ByteBuffer buffer) {
buffer.put((byte)((color >> 16) & 0xFF)).put((byte)((color >> 8) & 0xFF)).put((byte)(color & 0xFF));
if(alpha) buffer.put((byte)((color >> 24) & 0xFF));
}
public static void write(int index, int color, boolean alpha, ByteBuffer buffer) {
buffer.put(index, (byte)((color >> 16) & 0xFF)).put(index + 1, (byte)((color >> 8) & 0xFF)).put(index + 2, (byte)(color & 0xFF));
if(alpha) buffer.put(index + 3, (byte)((color >> 24) & 0xFF));
}
public static void write(int color, boolean alpha, FloatBuffer buffer) {
buffer.put(((color >> 16) & 0xFF) * DEVIDER).put(((color >> 8) & 0xFF) * DEVIDER).put((color & 0xFF) * DEVIDER);
if(alpha) buffer.put(((color >> 24) & 0xFF) * DEVIDER);
}
public static void write(int index, int color, boolean alpha, FloatBuffer buffer) {
buffer.put(index, ((color >> 16) & 0xFF) * DEVIDER).put(index + 1, ((color >> 8) & 0xFF) * DEVIDER).put(index + 2, (color & 0xFF) * DEVIDER);
if(alpha) buffer.put(index + 3, ((color >> 24) & 0xFF) * DEVIDER);
}
public static void write(int color, boolean alpha, FloatList list) {
list.add(((color >> 16) & 0xFF) * DEVIDER);
list.add(((color >> 8) & 0xFF) * DEVIDER);
list.add((color & 0xFF) * DEVIDER);
if(alpha) list.add(((color >> 24) & 0xFF) * DEVIDER);
}
public static int read(ByteBuffer buffer, boolean alpha) { return alpha ? rgb(buffer.get(), buffer.get(), buffer.get()) : rgb(buffer.get(), buffer.get(), buffer.get(), buffer.get()); }
public static int read(ByteBuffer buffer, int index, boolean alpha) { return alpha ? rgb(buffer.get(index), buffer.get(index + 1), buffer.get(index + 2)) : rgb(buffer.get(index), buffer.get(index + 1), buffer.get(index + 2), buffer.get(index + 3)); }
public static int read(FloatBuffer buffer, boolean alpha) { return alpha ? rgb(buffer.get(), buffer.get(), buffer.get()) : rgb(buffer.get(), buffer.get(), buffer.get(), buffer.get()); }
public static int read(FloatBuffer buffer, int index, boolean alpha) { return alpha ? rgb(buffer.get(index), buffer.get(index + 1), buffer.get(index + 2)) : rgb(buffer.get(index), buffer.get(index + 1), buffer.get(index + 2), buffer.get(index + 3)); }
public static boolean needsDarkColor(int rgba) { return getBrightness(rgba) >= 130; }
public static int getBrightness(int rgba) { return getBrightness((rgba >> 16) & 0xFF, (rgba >> 8) & 0xFF, rgba & 0xFF); }
public static int getBrightness(int r, int g, int b) { return (int)Math.sqrt((r * r * 0.241F) + (g * g * 0.691F) + (b * b * 0.068F)); }
public static int mix(int from, int to, float factor) {
float weight0 = (1F - factor);
float weight1 = factor;
int r = (int)((((from >> 16) & 0xFF) * weight0) + (((to >> 16) & 0xFF) * weight1));
int g = (int)((((from >> 8) & 0xFF) * weight0) + (((to >> 8) & 0xFF) * weight1));
int b = (int)(((from & 0xFF) * weight0) + ((to & 0xFF) * weight1));
int a = (int)((((from >> 24) & 0xFF) * weight0) + (((to >> 24) & 0xFF) * weight1));
return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | b & 0xFF;
}
public static int rgb(int rgb) { return rgb | (255 << 24); }
public static int rgb(int r, int g, int b) { return A | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF); }
public static int rgb(float r, float g, float b) { return rgb((int)(r * 255F + 0.5F), (int)(g * 255F + 0.5F), (int)(b * 255F + 0.5F)); }
public static int rgb(int r, int g, int b, int a) { return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF); }
public static int rgb(float r, float g, float b, float a) { return rgb((int)(r * 255F + 0.5F), (int)(g * 255F + 0.5F), (int)(b * 255F + 0.5F), (int)(b * 255F + 0.5F)); }
public static int setR(int rgba, int r) { return rgba & ~R | ((r & 0xFF) << 16); }
public static int setR(int rgba, float r) { return rgba & ~R | (((int)(r * 255F + 0.5F)) << 16); }
public static int setG(int rgba, int g) { return rgba & ~G | ((g & 0xFF) << 8); }
public static int setG(int rgba, float g) { return rgba & ~G | (((int)(g * 255F + 0.5F)) << 8); }
public static int setB(int rgba, int b) { return rgba & ~B | (b & 0xFF); }
public static int setB(int rgba, float b) { return rgba & ~B | ((int)(b * 255F + 0.5F)); }
public static int setA(int rgba, int a) { return rgba & ~A | ((a & 0xFF) << 24); }
public static int setA(int rgba, float a) { return rgba & ~A | (((int)(a * 255F + 0.5F)) << 24); }
public static int getR(int rgba) { return (rgba >> 16) & 0xFF; }
public static float getRF(int rgba) { return ((rgba >> 16) & 0xFF) * DEVIDER; }
public static int getG(int rgba) { return (rgba >> 8) & 0xFF; }
public static float getGF(int rgba) { return ((rgba >> 8) & 0xFF) * DEVIDER; }
public static int getB(int rgba) { return rgba & 0xFF; }
public static float getBF(int rgba) { return (rgba & 0xFF) * DEVIDER; }
public static int getA(int rgba) { return (rgba >> 24) & 0xFF; }
public static float getAF(int rgba) { return ((rgba >> 24) & 0xFF) * DEVIDER; }
public static int parse(String input) {
try { return Long.decode(input).intValue(); }
catch (Exception e) { return -1; }
}
public static int parse(String input, int defaultValue) {
try { return Long.decode(input).intValue(); }
catch (Exception e) { return defaultValue; }
}
public static String toHex(int rgba, boolean alpha) { return "0x"+(alpha ? Long.toHexString(1 << 32 | rgba & SIGN) : Integer.toHexString((1 << 24) | (rgba & ~A))).substring(1); }
public static String toHTML(int rgba, boolean alpha) { return "#"+(alpha ? Long.toHexString(1 << 32 | rgba & SIGN) : Integer.toHexString((1 << 24) | (rgba & ~A))).substring(1); }
}