diff --git a/gradle.properties b/gradle.properties index b261586..84c3308 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ org.gradle.jvmargs=-Xmx2G -lwjglVersion = 3.4.2 +lwjglVersion = 3.3.4 lwjglNatives = natives-windows \ No newline at end of file diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/buffer/VertexBuffer.java b/src/graphics/java/speiger/src/coreengine/graphics/api/buffer/VertexBuffer.java index f1700bc..da73d85 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/api/buffer/VertexBuffer.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/buffer/VertexBuffer.java @@ -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> data) { return fill(0, data); } public abstract VertexBuffer fill(int offset, List> data); diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/compute/BarrierType.java b/src/graphics/java/speiger/src/coreengine/graphics/api/compute/BarrierType.java new file mode 100644 index 0000000..fe15833 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/compute/BarrierType.java @@ -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; +} diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/compute/ComputePipeline.java b/src/graphics/java/speiger/src/coreengine/graphics/api/compute/ComputePipeline.java new file mode 100644 index 0000000..e511027 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/compute/ComputePipeline.java @@ -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 uniforms, List textures) { + + public static Builder builder(ID id) { + return new Builder(id); + } + + public static class Builder { + ID id; + ID shader; + ObjectList uniforms = new ObjectArrayList<>(); + ObjectList 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); + } + } +} diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/core/CommandBuffer.java b/src/graphics/java/speiger/src/coreengine/graphics/api/core/CommandBuffer.java new file mode 100644 index 0000000..9d1f419 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/core/CommandBuffer.java @@ -0,0 +1,5 @@ +package speiger.src.coreengine.graphics.api.core; + +public interface CommandBuffer { + int commandCount(); +} diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/core/ComputeCommandBuffer.java b/src/graphics/java/speiger/src/coreengine/graphics/api/core/ComputeCommandBuffer.java new file mode 100644 index 0000000..ac4b25f --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/core/ComputeCommandBuffer.java @@ -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(); +} diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/core/Graphics.java b/src/graphics/java/speiger/src/coreengine/graphics/api/core/Graphics.java index 199a6cd..745219f 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/api/core/Graphics.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/core/Graphics.java @@ -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(); diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/core/GraphicsCommandBuffer.java b/src/graphics/java/speiger/src/coreengine/graphics/api/core/GraphicsCommandBuffer.java index a5b5a5c..8a3f537 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/api/core/GraphicsCommandBuffer.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/core/GraphicsCommandBuffer.java @@ -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(); } diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/core/GraphicsCommandQueue.java b/src/graphics/java/speiger/src/coreengine/graphics/api/core/GraphicsCommandQueue.java index c74ab6b..ebbe2d4 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/api/core/GraphicsCommandQueue.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/core/GraphicsCommandQueue.java @@ -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(); } \ No newline at end of file diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/core/GraphicsDevice.java b/src/graphics/java/speiger/src/coreengine/graphics/api/core/GraphicsDevice.java index 0ac718d..23187b2 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/api/core/GraphicsDevice.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/core/GraphicsDevice.java @@ -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 pipelines); + TimeQueryPool createQueryPool(int size); + void preloadPipelines(List graphics, List compute); } diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/core/GraphicsFence.java b/src/graphics/java/speiger/src/coreengine/graphics/api/core/GraphicsFence.java new file mode 100644 index 0000000..b34b64a --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/core/GraphicsFence.java @@ -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); +} diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/core/TimeQueryPool.java b/src/graphics/java/speiger/src/coreengine/graphics/api/core/TimeQueryPool.java new file mode 100644 index 0000000..6cb15d8 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/core/TimeQueryPool.java @@ -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); +} diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/shader/CompiledPipeline.java b/src/graphics/java/speiger/src/coreengine/graphics/api/shader/CompiledPipeline.java deleted file mode 100644 index 98a7a38..0000000 --- a/src/graphics/java/speiger/src/coreengine/graphics/api/shader/CompiledPipeline.java +++ /dev/null @@ -1,5 +0,0 @@ -package speiger.src.coreengine.graphics.api.shader; - -public interface CompiledPipeline { - -} diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/shader/ShaderPipeline.java b/src/graphics/java/speiger/src/coreengine/graphics/api/shader/ShaderPipeline.java index c823fd3..39486f3 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/api/shader/ShaderPipeline.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/shader/ShaderPipeline.java @@ -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 shaders, List attributes, List uniforms, List textures, RasterizerState rasterizer, ColorTarget colorTarget, DepthTarget depthTarget) { +public record ShaderPipeline(ID id, Map shaders, List attributes, List uniforms, List 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 shaders, List shaders = new EnumMap<>(ShaderType.class); ObjectList attributes = new ObjectArrayList<>(); - ObjectList uniforms = new ObjectArrayList<>(); + ObjectList uniforms = new ObjectArrayList<>(); ObjectList 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 shaders, List shaders, List 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(attributes))); - return owner; - } - } } diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/shader/VertexBinding.java b/src/graphics/java/speiger/src/coreengine/graphics/api/shader/VertexBinding.java index 380171d..bf47f62 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/api/shader/VertexBinding.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/shader/VertexBinding.java @@ -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 attributes) { - +public record VertexBinding(int bindingIndex, VertexLayout layout, int instanceOffset) { + public ObjectIterable elements() { + return layout.elements(); + } } diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/shader/states/Bindings.java b/src/graphics/java/speiger/src/coreengine/graphics/api/shader/states/Bindings.java index 6d37533..a593613 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/api/shader/states/Bindings.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/shader/states/Bindings.java @@ -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; + } } diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/shader/states/ShaderType.java b/src/graphics/java/speiger/src/coreengine/graphics/api/shader/states/ShaderType.java index d589ebd..41f3de8 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/api/shader/states/ShaderType.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/shader/states/ShaderType.java @@ -5,5 +5,6 @@ public enum ShaderType { FRAGMENT, GEOMETRY, TESSELATION_CONTROL, - TESSELATION_EVALUATION; + TESSELATION_EVALUATION, + COMPUTE; } diff --git a/src/graphics/java/speiger/src/coreengine/graphics/api/texture/drawable/Drawable.java b/src/graphics/java/speiger/src/coreengine/graphics/api/texture/drawable/Drawable.java new file mode 100644 index 0000000..2016de9 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/graphics/api/texture/drawable/Drawable.java @@ -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 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; + } + } } diff --git a/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLFence.java b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLFence.java new file mode 100644 index 0000000..73e1874 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLFence.java @@ -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); + } + +} diff --git a/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLGraphics.java b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLGraphics.java index feafd72..8e7050e 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLGraphics.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLGraphics.java @@ -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; diff --git a/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLGraphicsDevice.java b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLGraphicsDevice.java index e91f0a6..5d8ad6f 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLGraphicsDevice.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLGraphicsDevice.java @@ -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 IMPORTS = Object2ObjectMap.builder().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 pipelines) { + public TimeQueryPool createQueryPool(int size) { + return new GLTimeQueryPool(size); + } + + @Override + public void preloadPipelines(List graphics, List 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 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 uniforms = new ShaderStorage<>(); - ShaderStorage samplers = new ShaderStorage<>(); - for(TextureBinding binding : pipeline.textures()) { + return generateShaderInstance(program, pipeline::textures, pipeline::uniforms); + } + + private ShaderInstance generateShaderInstance(int program, Supplier> textureProvider, Supplier> uniformProvider) { + ShaderStorage uniforms = new ShaderStorage<>(); + ShaderStorage storages = new ShaderStorage<>(); + ShaderStorage samplers = new ShaderStorage<>(); + ShaderStorage 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 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 crc) { + ByteBuffer data = cache.get(pipeline, crc); if(data == null) return false; GL41.glProgramBinary(programId, data.getInt(), data); MemoryUtil.memFree(data); diff --git a/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLImmidateCommandBuffer.java b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLImmidateCommandBuffer.java index b8b5de6..ea68e2c 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLImmidateCommandBuffer.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLImmidateCommandBuffer.java @@ -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(); diff --git a/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLImmidateComputeCommandBuffer.java b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLImmidateComputeCommandBuffer.java new file mode 100644 index 0000000..83bcb64 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLImmidateComputeCommandBuffer.java @@ -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; + } + +} diff --git a/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLRecordingCommandBuffer.java b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLRecordingCommandBuffer.java index e025481..42c8b2a 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLRecordingCommandBuffer.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLRecordingCommandBuffer.java @@ -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); } } } diff --git a/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLRecordingComputeCommandBuffer.java b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLRecordingComputeCommandBuffer.java new file mode 100644 index 0000000..7923315 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLRecordingComputeCommandBuffer.java @@ -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 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); + } + } +} diff --git a/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLSurface.java b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLSurface.java index 46aaecb..ace3c00 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLSurface.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLSurface.java @@ -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(); } diff --git a/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLTimeQueryPool.java b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLTimeQueryPool.java new file mode 100644 index 0000000..fdeccd3 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/graphics/opengl/core/GLTimeQueryPool.java @@ -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(); + } + +} diff --git a/src/graphics/java/speiger/src/coreengine/graphics/opengl/mesh/GLMesh.java b/src/graphics/java/speiger/src/coreengine/graphics/opengl/mesh/GLMesh.java index 14d9f39..b8a4ec1 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/opengl/mesh/GLMesh.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/opengl/mesh/GLMesh.java @@ -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()); } } diff --git a/src/graphics/java/speiger/src/coreengine/graphics/opengl/shader/ShaderInstance.java b/src/graphics/java/speiger/src/coreengine/graphics/opengl/shader/ShaderInstance.java index 53d6269..58819bd 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/opengl/shader/ShaderInstance.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/opengl/shader/ShaderInstance.java @@ -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 uniforms, ShaderStorage samplers) { - public record UniformObject(int slot) {} - public record SamplerObject(int unit) {} +public record ShaderInstance(int programId, ShaderStorage uniforms, ShaderStorage storages, ShaderStorage samplers, ShaderStorage buffers) { + public record BufferObject(int slot) {} + public record TextureObject(int unit) {} public static class ShaderStorage { Map byName = new Object2ObjectOpenHashMap<>(); diff --git a/src/graphics/java/speiger/src/coreengine/graphics/opengl/shader/ShaderTracker.java b/src/graphics/java/speiger/src/coreengine/graphics/opengl/shader/ShaderTracker.java index cc2dea7..0c745f9 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/opengl/shader/ShaderTracker.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/opengl/shader/ShaderTracker.java @@ -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 knownShaders = new ObjectArrayList(); 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; diff --git a/src/graphics/java/speiger/src/coreengine/graphics/opengl/utils/GLUtils.java b/src/graphics/java/speiger/src/coreengine/graphics/opengl/utils/GLUtils.java index fcdfcb9..221ebf6 100644 --- a/src/graphics/java/speiger/src/coreengine/graphics/opengl/utils/GLUtils.java +++ b/src/graphics/java/speiger/src/coreengine/graphics/opengl/utils/GLUtils.java @@ -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; + }; + } } diff --git a/src/graphics/java/speiger/src/coreengine/input/device/AbstractDevice.java b/src/graphics/java/speiger/src/coreengine/input/device/AbstractDevice.java new file mode 100644 index 0000000..4947d68 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/input/device/AbstractDevice.java @@ -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 implements InputDevice { + protected Long2ObjectMap> queues = new Long2ObjectConcurrentOpenHashMap<>(); + protected Long2ObjectMap 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 queue = queues.get(windowId); + if(queue == null) return; + queue.add(task); + } + + @Override + public void processInput(long windowId) { + Deque queue = queues.get(windowId); + if(queue == null) return; + while(!queue.isEmpty()) { + process(queue.poll()); + } + } + + public T get(long windowId) { + return windowData.get(windowId); + } +} diff --git a/src/graphics/java/speiger/src/coreengine/input/device/FileDrop.java b/src/graphics/java/speiger/src/coreengine/input/device/FileDrop.java new file mode 100644 index 0000000..f94634f --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/input/device/FileDrop.java @@ -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 { + 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 paths = new ObjectArrayList<>(count); + for(int i = 0;i { + 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 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)); + } + } +} \ No newline at end of file diff --git a/src/graphics/java/speiger/src/coreengine/input/device/Keyboard.java b/src/graphics/java/speiger/src/coreengine/input/device/Keyboard.java new file mode 100644 index 0000000..5409924 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/input/device/Keyboard.java @@ -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 { + 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 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)); + } + } +} \ No newline at end of file diff --git a/src/graphics/java/speiger/src/coreengine/input/device/Mouse.java b/src/graphics/java/speiger/src/coreengine/input/device/Mouse.java new file mode 100644 index 0000000..3d44d49 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/input/device/Mouse.java @@ -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 { + 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(); + } + } + } +} \ No newline at end of file diff --git a/src/graphics/java/speiger/src/coreengine/input/events/FileEvents.java b/src/graphics/java/speiger/src/coreengine/input/events/FileEvents.java new file mode 100644 index 0000000..12f47b0 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/input/events/FileEvents.java @@ -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[] 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 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); } + } +} diff --git a/src/graphics/java/speiger/src/coreengine/input/events/JoystickEvent.java b/src/graphics/java/speiger/src/coreengine/input/events/JoystickEvent.java new file mode 100644 index 0000000..be4d471 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/input/events/JoystickEvent.java @@ -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; } + } +} diff --git a/src/graphics/java/speiger/src/coreengine/input/events/KeyEvent.java b/src/graphics/java/speiger/src/coreengine/input/events/KeyEvent.java new file mode 100644 index 0000000..fdfbabe --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/input/events/KeyEvent.java @@ -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; } + } +} diff --git a/src/graphics/java/speiger/src/coreengine/input/events/MouseEvent.java b/src/graphics/java/speiger/src/coreengine/input/events/MouseEvent.java new file mode 100644 index 0000000..3dbc84e --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/input/events/MouseEvent.java @@ -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; } + } +} diff --git a/src/graphics/java/speiger/src/coreengine/input/window/GLFWUtil.java b/src/graphics/java/speiger/src/coreengine/input/window/GLFWUtil.java new file mode 100644 index 0000000..e39824e --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/input/window/GLFWUtil.java @@ -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)); + } + } +} diff --git a/src/graphics/java/speiger/src/coreengine/input/window/IWindowListener.java b/src/graphics/java/speiger/src/coreengine/input/window/IWindowListener.java new file mode 100644 index 0000000..b6f1d9a --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/input/window/IWindowListener.java @@ -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; + } +} diff --git a/src/graphics/java/speiger/src/coreengine/input/window/Monitor.java b/src/graphics/java/speiger/src/coreengine/input/window/Monitor.java new file mode 100644 index 0000000..3f7841d --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/input/window/Monitor.java @@ -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 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 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 createMonitors() { + Long2ObjectMap monitors = new Long2ObjectLinkedOpenHashMap<>(); + PointerBuffer buffer = GLFW.glfwGetMonitors(); + for(int i = 0,m=buffer.limit();i callbacks = new ObjectArrayList<>(); + List 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 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 { + GLFW.glfwDestroyWindow(id); + callbacks.forEach(WindowCallback::destroy); + callbacks.clear(); + }); + } + + @SuppressWarnings("unchecked") + public WindowCallback addCallback(T listener, ReloadFunction function) { + if(!manager.isManagerThread()) return manager.joinOnWindowThread(() -> addCallback(listener, function)); + WindowCallback callback = new WindowCallback(listener, (ReloadFunction)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; } +} diff --git a/src/graphics/java/speiger/src/coreengine/input/window/WindowBuilder.java b/src/graphics/java/speiger/src/coreengine/input/window/WindowBuilder.java new file mode 100644 index 0000000..c5a9335 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/input/window/WindowBuilder.java @@ -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); + } + +} \ No newline at end of file diff --git a/src/graphics/java/speiger/src/coreengine/input/window/WindowCallback.java b/src/graphics/java/speiger/src/coreengine/input/window/WindowCallback.java new file mode 100644 index 0000000..f34c6fb --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/input/window/WindowCallback.java @@ -0,0 +1,40 @@ +package speiger.src.coreengine.input.window; + +import org.lwjgl.system.Callback; +import org.lwjgl.system.CallbackI; + +public class WindowCallback { + ReloadFunction function; + CallbackI listener; + Callback callback; + + public WindowCallback(CallbackI listener, ReloadFunction 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 { + Callback applyListener(long window, T listener); + + } + public static interface SimpleReloadFunction extends ReloadFunction { + Callback applyListener(T listener); + @Override + default Callback applyListener(long window, T listener) { return applyListener(listener); } + } +} diff --git a/src/graphics/java/speiger/src/coreengine/input/window/WindowManager.java b/src/graphics/java/speiger/src/coreengine/input/window/WindowManager.java new file mode 100644 index 0000000..824a060 --- /dev/null +++ b/src/graphics/java/speiger/src/coreengine/input/window/WindowManager.java @@ -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 MANAGER = ScopedValue.newInstance(); + BlockingQueue tasks = new LinkedBlockingQueue<>(); + Graphics graphcis; + + Long2ObjectMap windows = new Long2ObjectConcurrentOpenHashMap<>(); + Long2ObjectMap monitors; + Window activeWindow; + Window primaryWindow; + + + List callbacks = new ObjectArrayList<>(); + Callback monitorTracker; + List 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 joinOnWindowThread(Supplier provider) { + return CompletableFuture.supplyAsync(provider, tasks::add).join(); + } + + public CompletableFuture runOnWindowThread(Runnable run) { + return CompletableFuture.runAsync(run, tasks::add); + } + + public CompletableFuture runOnWindowThread(Supplier 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 WindowCallback addCallback(T listener, SimpleReloadFunction function) { + WindowCallback callback = new WindowCallback(listener, (SimpleReloadFunction)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 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 largest) { + largest = next; + mon = monitor; + } + } + return mon; + } +} diff --git a/src/main/java/speiger/src/coreengine/NewInputTest.java b/src/main/java/speiger/src/coreengine/NewInputTest.java index 77865af..85e29df 100644 --- a/src/main/java/speiger/src/coreengine/NewInputTest.java +++ b/src/main/java/speiger/src/coreengine/NewInputTest.java @@ -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(); diff --git a/src/main/java/speiger/src/coreengine/NewRenderEngineTest.java b/src/main/java/speiger/src/coreengine/NewRenderEngineTest.java index 70f1e2f..5087739 100644 --- a/src/main/java/speiger/src/coreengine/NewRenderEngineTest.java +++ b/src/main/java/speiger/src/coreengine/NewRenderEngineTest.java @@ -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(); } } diff --git a/src/main/java/speiger/src/coreengine/rendering/input/window/Window.java b/src/main/java/speiger/src/coreengine/rendering/input/window/Window.java index 2006ac0..ffba862 100644 --- a/src/main/java/speiger/src/coreengine/rendering/input/window/Window.java +++ b/src/main/java/speiger/src/coreengine/rendering/input/window/Window.java @@ -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 monitors; Long2ObjectMap windows = new Long2ObjectConcurrentOpenHashMap<>(); Window activeWindow; @@ -27,7 +28,8 @@ public class WindowManager { Callback monitorTracker; List 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); } } diff --git a/src/main/java/speiger/src/coreengine/utils/eventbus/EventBus.java b/src/main/java/speiger/src/coreengine/utils/eventbus/EventBus.java index 99b499c..90ba3d3 100644 --- a/src/main/java/speiger/src/coreengine/utils/eventbus/EventBus.java +++ b/src/main/java/speiger/src/coreengine/utils/eventbus/EventBus.java @@ -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, Listeners> listeners = new Object2ObjectOpenHashMap, Listeners>().synchronize(); - Object2ObjectMap> instances = new Object2ObjectConcurrentOpenHashMap<>(); - - public void register(Class event, Consumer listener) { - register(event, EventPriority.MEDIUM, listener); - } - - public void register(Class event, EventPriority priority, Consumer 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 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 listener = new MethodListener(LOOKUP.unreflect(t).bindTo(obj)); - Class 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 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 void find(Class clz, boolean superClasses, Function, T[]> mapper, Consumer 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[] listeners = getListeners(event.getClass()).getListeners(); - if(listeners.length <= 0) return; - int index = 0; - try { - for(;index 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 Consumer cast(Object obj) { - return (Consumer) obj; - } - - @SuppressWarnings("unchecked") - private Class castClass(Class clz) { - return (Class) clz; - } - - public static record EventListener(Class event, Consumer listener) {} - public static record MethodListener(MethodHandle handle) implements Consumer { - @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, Listeners> listeners = new Object2ObjectOpenHashMap, Listeners>().synchronize(); + Object2ObjectMap> instances = new Object2ObjectConcurrentOpenHashMap<>(); + + public void register(Class event, Consumer listener) { + register(event, EventPriority.MEDIUM, listener); + } + + public void register(Class event, EventPriority priority, Consumer 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 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 listener = new MethodListener(LOOKUP.unreflect(t).bindTo(obj)); + Class 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 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 void find(Class clz, boolean superClasses, Function, T[]> mapper, Consumer 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[] listeners = getListeners(event.getClass()).getListeners(); + if(listeners.length <= 0) return; + int index = 0; + try { + for(;index 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 Consumer cast(Object obj) { + return (Consumer) obj; + } + + @SuppressWarnings("unchecked") + private Class castClass(Class clz) { + return (Class) clz; + } + + public static record EventListener(Class event, Consumer listener) {} + public static record MethodListener(MethodHandle handle) implements Consumer { + @Override + public void accept(Event t) { + try { handle.invoke(t); } + catch(Throwable e) { e.printStackTrace(); } + } + } +} diff --git a/src/math/java/speiger/src/coreengine/math/color/ColorSpaces.java b/src/math/java/speiger/src/coreengine/math/color/ColorSpaces.java new file mode 100644 index 0000000..f0c87c8 --- /dev/null +++ b/src/math/java/speiger/src/coreengine/math/color/ColorSpaces.java @@ -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; + } +} diff --git a/src/math/java/speiger/src/coreengine/math/color/ColorUtils.java b/src/math/java/speiger/src/coreengine/math/color/ColorUtils.java new file mode 100644 index 0000000..99f4446 --- /dev/null +++ b/src/math/java/speiger/src/coreengine/math/color/ColorUtils.java @@ -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); } +}